├── Godeps ├── _workspace │ └── .gitignore ├── Readme └── Godeps.json ├── .gitignore ├── testdata ├── tty_tiny.ova └── ttylinux-pc_i486-16.1.iso ├── SSPI ├── README.md ├── sspi_unsupported.go ├── LICENSE.txt └── sspi.go ├── photon ├── photon_suite_test.go ├── lightwave │ ├── oidcclient_sspi_unsupported.go │ ├── lightwave_suite_test.go │ ├── jwttoken.go │ ├── jwttoken_test.go │ ├── oidcclient_sspi.go │ └── oidcclient.go ├── util_test.go ├── info.go ├── client_test.go ├── infrastructure.go ├── datastores.go ├── info_test.go ├── error_test.go ├── datastores_test.go ├── hosts.go ├── infrastructure_test.go ├── zones.go ├── routers.go ├── networks.go ├── tasks.go ├── flavors.go ├── internal │ └── mocks │ │ └── httpserver.go ├── util.go ├── subnets.go ├── disks.go ├── auth_test.go ├── auth.go ├── services.go ├── flavors_test.go ├── infrastructure_hosts.go ├── routers_test.go ├── networks_test.go ├── images.go ├── client.go ├── system.go ├── zones_test.go ├── helpers_test.go ├── subnets_test.go ├── mocks_test.go ├── system_test.go ├── services_test.go ├── tenants.go ├── disks_test.go └── hosts_test.go ├── ci └── jenkins.sh ├── DEVELOP.md └── README.md /Godeps/_workspace/.gitignore: -------------------------------------------------------------------------------- 1 | /pkg 2 | /bin 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Diff files 2 | *.orig 3 | 4 | .idea 5 | src 6 | pkg 7 | bin 8 | -------------------------------------------------------------------------------- /testdata/tty_tiny.ova: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-archive/photon-controller-go-sdk/HEAD/testdata/tty_tiny.ova -------------------------------------------------------------------------------- /testdata/ttylinux-pc_i486-16.1.iso: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vmware-archive/photon-controller-go-sdk/HEAD/testdata/ttylinux-pc_i486-16.1.iso -------------------------------------------------------------------------------- /Godeps/Readme: -------------------------------------------------------------------------------- 1 | This directory tree is generated automatically by godep. 2 | 3 | Please do not edit. 4 | 5 | See https://github.com/tools/godep for more information. 6 | -------------------------------------------------------------------------------- /SSPI/README.md: -------------------------------------------------------------------------------- 1 | SSPI implementation in this directory is taken from https://github.com/WillHipschman/sspi-prototype (git commit: 4feee8f2e75857bb51345fdfc8e3c091edc959b4) 2 | Implementation is modified to make sure that it only compiles on Windows. 3 | For non-Windows platforms, please look at sspi_unspported.go 4 | -------------------------------------------------------------------------------- /Godeps/Godeps.json: -------------------------------------------------------------------------------- 1 | { 2 | "ImportPath": "github.com/vmware/photon-controller-go-sdk", 3 | "GoVersion": "go1.5", 4 | "Packages": [ 5 | "./..." 6 | ], 7 | "Deps": [ 8 | { 9 | "ImportPath": "github.com/onsi/ginkgo", 10 | "Comment": "v1.2.0-11-gd94e2f4", 11 | "Rev": "d94e2f4000332f62b356ecb2840c98f4218f5358" 12 | }, 13 | { 14 | "ImportPath": "github.com/onsi/gomega", 15 | "Comment": "v1.0-52-ga2ab864", 16 | "Rev": "a2ab8644e0b6a33df2cbe44673bd0f6ebba9abc3" 17 | } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /SSPI/sspi_unsupported.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | // +build !windows 11 | 12 | package SSPI 13 | -------------------------------------------------------------------------------- /photon/photon_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon_test 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | 16 | "testing" 17 | ) 18 | 19 | func TestPhoton(t *testing.T) { 20 | RegisterFailHandler(Fail) 21 | RunSpecs(t, "Go SDK Suite") 22 | } 23 | -------------------------------------------------------------------------------- /photon/lightwave/oidcclient_sspi_unsupported.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | // +build !windows 11 | 12 | package lightwave 13 | 14 | import "errors" 15 | 16 | func (client *OIDCClient) GetTokensFromWindowsLogInContext() (tokens *OIDCTokenResponse, err error) { 17 | return nil, errors.New("Not supported on this OS") 18 | } 19 | -------------------------------------------------------------------------------- /photon/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | ) 16 | 17 | type options struct { 18 | A int `urlParam:"a"` 19 | B string `urlParam:"b"` 20 | } 21 | 22 | var _ = Describe("Utils", func() { 23 | It("GetQueryString", func() { 24 | opts := &options{5, "a test"} 25 | query := getQueryString(opts) 26 | Expect(query).Should(Equal("?a=5&b=a+test")) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /photon/info.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "encoding/json" 14 | ) 15 | 16 | type InfoAPI struct { 17 | client *Client 18 | } 19 | 20 | var infoUrl = rootUrl + "/info" 21 | 22 | // Get info 23 | func (api *InfoAPI) Get() (info *Info, err error) { 24 | res, err := api.client.restClient.Get(api.client.Endpoint+infoUrl, api.client.options.TokenOptions) 25 | if err != nil { 26 | return 27 | } 28 | 29 | defer res.Body.Close() 30 | 31 | res, err = getError(res) 32 | if err != nil { 33 | return 34 | } 35 | 36 | info = new(Info) 37 | err = json.NewDecoder(res.Body).Decode(info) 38 | return 39 | } 40 | -------------------------------------------------------------------------------- /photon/client_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "fmt" 14 | "strings" 15 | 16 | . "github.com/onsi/ginkgo" 17 | . "github.com/onsi/gomega" 18 | "log" 19 | "os" 20 | ) 21 | 22 | var _ = Describe("Client", func() { 23 | Describe("NewClient", func() { 24 | It("Trims trailing '/' from endpoint", func() { 25 | endpointList := []string{ 26 | "http://10.146.1.0/", 27 | "http://10.146.1.0", 28 | } 29 | 30 | for index, endpoint := range endpointList { 31 | client := NewClient(endpoint, nil, log.New(os.Stderr, "", log.LstdFlags)) 32 | Expect(client.Endpoint).To( 33 | Equal(strings.TrimRight(endpoint, "/")), 34 | fmt.Sprintf("Test data index: %v", index)) 35 | } 36 | }) 37 | }) 38 | }) 39 | -------------------------------------------------------------------------------- /ci/jenkins.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | if [ -z "$WORKSPACE" ] 5 | then 6 | pushd $(dirname $BASH_SOURCE) 7 | export WORKSPACE=$(git rev-parse --show-toplevel) 8 | echo Assume default WORKSPACE $WORKSPACE 9 | popd 10 | fi 11 | 12 | # Go expects to have a "Go workspace" (GOPATH) present in order to build. 13 | # This workspace is not supposed to be checked in, so we must create it at 14 | # build time. 15 | cd $WORKSPACE 16 | export GOPATH=$WORKSPACE 17 | 18 | # $WORKSPACE will be the root of the git repo that is pulled in by Jenkins. 19 | # We need to move its contents into the expected package path inside 20 | # $GOPATH/src (defined by PACKAGESRC) before we can build. 21 | PACKAGESRC=src/github.com/vmware/photon-controller-go-sdk 22 | 23 | # clean the directory 24 | if [ -n "$WORKSPACE" ] 25 | then 26 | rm -rf $WORKSPACE/src $WORKSPACE/pkg $WORKSPACE/bin 27 | fi 28 | 29 | REPOFILES=(*) 30 | echo ${REPOFILES[*]} 31 | mkdir -p $PACKAGESRC 32 | cp -r ${REPOFILES[*]} $PACKAGESRC/ 33 | 34 | go get github.com/tools/godep 35 | pushd $PACKAGESRC 36 | $GOPATH/bin/godep restore 37 | 38 | # Fail if there is any go fmt error. 39 | if [[ -n "$(gofmt -l photon)" ]]; then 40 | echo Fix gofmt errors 41 | gofmt -d photon 42 | exit 1 43 | fi 44 | 45 | export cmd="go test ./... -v" 46 | # Test against a real external endpoint requires longer timeout than GO's default 600s 47 | if [ "" != "$TEST_ENDPOINT" ] 48 | then 49 | export cmd="$cmd -timeout 1800s" 50 | fi 51 | # Build and run tests 52 | $cmd -ginkgo.noColor -ginkgo.slowSpecThreshold=60 -ginkgo.v 53 | -------------------------------------------------------------------------------- /SSPI/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012 The Go Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /photon/lightwave/lightwave_suite_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package lightwave 11 | 12 | import ( 13 | "os" 14 | "testing" 15 | 16 | . "github.com/onsi/ginkgo" 17 | . "github.com/onsi/gomega" 18 | 19 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 20 | ) 21 | 22 | func TestPhotonLightWave(t *testing.T) { 23 | RegisterFailHandler(Fail) 24 | RunSpecs(t, "Go SDK LightWave Suite") 25 | } 26 | 27 | func testSetupFakeServer() (client *OIDCClient, server *mocks.Server) { 28 | server = mocks.NewTlsTestServer() 29 | 30 | options := &OIDCClientOptions{ 31 | IgnoreCertificate: true, 32 | } 33 | 34 | client = NewOIDCClient(server.HttpServer.URL, options, nil) 35 | return 36 | } 37 | 38 | func testSetupRealServer(options *OIDCClientOptions) (client *OIDCClient, username string, password string) { 39 | // If LIGHTWAVE_ENDPOINT env var is set, return an empty server and point 40 | // the client to LIGHTWAVE_ENDPOINT. This lets us run tests as integration tests 41 | if len(os.Getenv("LIGHTWAVE_ENDPOINT")) == 0 { 42 | return 43 | } 44 | 45 | client = NewOIDCClient(os.Getenv("LIGHTWAVE_ENDPOINT"), options, nil) 46 | username = os.Getenv("LIGHTWAVE_USERNAME") 47 | password = os.Getenv("LIGHTWAVE_PASSWORD") 48 | return 49 | } 50 | -------------------------------------------------------------------------------- /photon/infrastructure.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for Infrastructure API. 18 | type InfraAPI struct { 19 | client *Client 20 | } 21 | 22 | var infraUrl string = rootUrl + "/infrastructure" 23 | 24 | // Synchronizes hosts configurations 25 | func (api *InfraAPI) SyncHostsConfig() (task *Task, err error) { 26 | res, err := api.client.restClient.Post( 27 | api.client.Endpoint+infraUrl+"/sync-hosts-config", 28 | "application/json", 29 | bytes.NewReader([]byte("")), 30 | api.client.options.TokenOptions) 31 | if err != nil { 32 | return 33 | } 34 | defer res.Body.Close() 35 | 36 | task, err = getTask(getError(res)) 37 | return 38 | } 39 | 40 | // Set image datastores. 41 | func (api *InfraAPI) SetImageDatastores(imageDatastores *ImageDatastores) (task *Task, err error) { 42 | body, err := json.Marshal(imageDatastores) 43 | if err != nil { 44 | return 45 | } 46 | 47 | res, err := api.client.restClient.Post( 48 | api.client.Endpoint+infraUrl+"/image-datastores", 49 | "application/json", 50 | bytes.NewReader(body), 51 | api.client.options.TokenOptions) 52 | if err != nil { 53 | return 54 | } 55 | defer res.Body.Close() 56 | 57 | task, err = getTask(getError(res)) 58 | return 59 | } 60 | -------------------------------------------------------------------------------- /photon/datastores.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "encoding/json" 14 | ) 15 | 16 | // DatastoresAPI is used by the client 17 | type DatastoresAPI struct { 18 | client *Client 19 | } 20 | 21 | var datastoresURL = rootUrl + "/infrastructure/datastores" 22 | 23 | // GetAll returns all datastores; requires system administrator privileges 24 | func (api *DatastoresAPI) GetAll() (result *Datastores, err error) { 25 | res, err := api.client.restClient.Get(api.client.Endpoint+datastoresURL, api.client.options.TokenOptions) 26 | if err != nil { 27 | return 28 | } 29 | defer res.Body.Close() 30 | res, err = getError(res) 31 | if err != nil { 32 | return 33 | } 34 | result = &Datastores{} 35 | err = json.NewDecoder(res.Body).Decode(result) 36 | return result, err 37 | } 38 | 39 | // Get returns a single datastore with the specified ID; requires system administrator privileges 40 | func (api *DatastoresAPI) Get(id string) (datastore *Datastore, err error) { 41 | res, err := api.client.restClient.Get(api.client.Endpoint+datastoresURL+"/"+id, api.client.options.TokenOptions) 42 | if err != nil { 43 | return 44 | } 45 | defer res.Body.Close() 46 | res, err = getError(res) 47 | if err != nil { 48 | return 49 | } 50 | var result Datastore 51 | err = json.NewDecoder(res.Body).Decode(&result) 52 | return &result, err 53 | } 54 | -------------------------------------------------------------------------------- /photon/info_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "github.com/onsi/ginkgo" 14 | "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = ginkgo.Describe("Info", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | ) 23 | 24 | ginkgo.BeforeEach(func() { 25 | server, client = testSetup() 26 | }) 27 | 28 | ginkgo.AfterEach(func() { 29 | server.Close() 30 | }) 31 | 32 | ginkgo.Describe("Get", func() { 33 | ginkgo.It("Get deployment info successfully", func() { 34 | baseVersion := "1.1.0" 35 | fullVersion := "1.1.0-bcea65f" 36 | gitCommitHash := "bcea65f" 37 | networkType := "SOFTWARE_DEFINED" 38 | server.SetResponseJson(200, 39 | Info{ 40 | BaseVersion: baseVersion, 41 | FullVersion: fullVersion, 42 | GitCommitHash: gitCommitHash, 43 | NetworkType: networkType, 44 | }) 45 | 46 | info, err := client.Info.Get() 47 | ginkgo.GinkgoT().Log(err) 48 | 49 | gomega.Expect(err).Should(gomega.BeNil()) 50 | gomega.Expect(info).ShouldNot(gomega.BeNil()) 51 | gomega.Expect(info.BaseVersion).Should(gomega.Equal(baseVersion)) 52 | gomega.Expect(info.FullVersion).Should(gomega.Equal(fullVersion)) 53 | gomega.Expect(info.GitCommitHash).Should(gomega.Equal(gitCommitHash)) 54 | gomega.Expect(info.NetworkType).Should(gomega.Equal(networkType)) 55 | }) 56 | }) 57 | }) 58 | -------------------------------------------------------------------------------- /photon/lightwave/jwttoken.go: -------------------------------------------------------------------------------- 1 | package lightwave 2 | 3 | import ( 4 | "encoding/base64" 5 | "encoding/json" 6 | "strings" 7 | ) 8 | 9 | type JWTToken struct { 10 | TokenId string `json:"jti"` 11 | Algorithm string `json:"alg"` 12 | Subject string `json:"sub"` 13 | Audience []string `json:"aud"` 14 | Groups []string `json:"groups"` 15 | Issuer string `json:"iss"` 16 | IssuedAt int64 `json:"iat"` 17 | Expires int64 `json:"exp"` 18 | Scope string `json:"scope"` 19 | TokenType string `json:"token_type"` 20 | TokenClass string `json:"token_class"` 21 | Tenant string `json:"tenant"` 22 | // It's possible to have more fields depending on how Lightwave defines the token. 23 | // This covers all the fields we currently have. 24 | } 25 | 26 | // A JSON web token is a set of Base64 encoded strings separated by a period (.) 27 | // When decoded, it will either be JSON text or a signature 28 | // Here we decode the strings into a single token structure. We do not parse the signature. 29 | func ParseTokenDetails(token string) (jwtToken *JWTToken) { 30 | jwtToken = &JWTToken{} 31 | 32 | chunks := strings.Split(token, ".") 33 | for _, chunk := range chunks { 34 | json_string, err := base64.RawURLEncoding.DecodeString(chunk) 35 | if err == nil { 36 | // Ignore errors. We expect that the signature is not JSON, 37 | // so unmarshalling it will fail. That's fine. We'll extract 38 | // all the data we can. 39 | _ = json.Unmarshal(json_string, &jwtToken) 40 | } 41 | } 42 | 43 | return jwtToken 44 | } 45 | 46 | // A JSON web token is a set of Base64 encoded strings separated by a period (.) 47 | // When decoded, it will either be JSON text or a signature 48 | // Here we parse the full JSON text. We do not parse the signature. 49 | func ParseRawTokenDetails(token string) (jwtToken []string, err error) { 50 | chunks := strings.Split(token, ".") 51 | for _, chunk := range chunks { 52 | jsonString, err := base64.RawURLEncoding.DecodeString(chunk) 53 | if err == nil { 54 | jwtToken = append(jwtToken, string(jsonString)) 55 | } 56 | } 57 | 58 | return jwtToken, err 59 | } 60 | -------------------------------------------------------------------------------- /DEVELOP.md: -------------------------------------------------------------------------------- 1 | # WARNING: Photon Controller GO SDK is no longer actively maintained by VMware. 2 | 3 | VMware has made the difficult decision to stop driving this project and therefore we will no longer actively respond 4 | to issues or pull requests. If you would like to take over maintaining this project independently from VMware, please 5 | let us know so we can add a link to your forked project here. 6 | 7 | # Requirements 8 | 9 | ## Go 1.5 or later 10 | 11 | Follow [the instructions](https://golang.org/doc/install) to install Go. Once 12 | the installation is complete, run: 13 | 14 | go version 15 | 16 | to verify that you have installed the version 1.5 or later. Then, create your 17 | [workspace](https://golang.org/doc/code.html#Workspaces) and set the `GOPATH` 18 | and environment variable to point to your workspace: 19 | 20 | mkdir $HOME/go 21 | export GOPATH=$HOME/go 22 | 23 | Add `$GOPATH/bin` to your `PATH`: 24 | 25 | export PATH=$PATH:$GOPATH/bin 26 | 27 | ## Godep 28 | 29 | Run: 30 | 31 | go get github.com/tools/godep 32 | 33 | and verify that the `godep` command is installed in `$GOPATH/bin`: 34 | 35 | # Checking out the source and building 36 | 37 | Clone the `photon-controller-go-sdk` repo under `$GOPATH/src/github.com/vmware`: 38 | 39 | mkdir -p $GOPATH/src/github.com/vmware 40 | cd $GOPATH/src/github.com/vmware 41 | git clone (github.com/vmware or gerrit)/photon-controller-go-sdk 42 | 43 | Then, restore dependencies and install the `ginkgo` command: 44 | 45 | cd $GOPATH/src/github.com/vmware/photon-controller-go-sdk 46 | godep restore 47 | go install github.com/onsi/ginkgo/ginkgo 48 | 49 | and verify that the `ginkgo` command is installed in `$GOPATH/bin`: 50 | 51 | ls $GOPATH/bin/ginkgo 52 | 53 | To compile, run: 54 | 55 | go build ./... 56 | 57 | # Testing 58 | 59 | Run: 60 | 61 | go test ./... -v 62 | 63 | to run the tests against a mock server. You can set `TEST_ENDPOINT` environment 64 | variable to run the tests against a real Photon Controller endpoint: 65 | 66 | # Need a longer timeout against a real server 67 | TEST_ENDPOINT=http://localhost:9080 go test ./... -v -timeout 1800s 68 | 69 | With `ginkgo`, you can run a subset of the tests: 70 | 71 | TEST_ENDPOINT=http://localhost:9080 ginkgo -r -focus Tenant -v 72 | -------------------------------------------------------------------------------- /photon/error_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "time" 14 | 15 | . "github.com/onsi/ginkgo" 16 | . "github.com/onsi/gomega" 17 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 18 | ) 19 | 20 | var _ = Describe("ErrorTesting", func() { 21 | var ( 22 | server *mocks.Server 23 | client *Client 24 | ) 25 | 26 | BeforeEach(func() { 27 | server, client = testSetup() 28 | }) 29 | 30 | AfterEach(func() { 31 | server.Close() 32 | }) 33 | 34 | It("TaskError", func() { 35 | // Unit test only 36 | if isIntegrationTest() { 37 | return 38 | } 39 | task := &Task{ID: "fake-id", State: "ERROR", Operation: "fake-op"} 40 | server.SetResponseJson(200, task) 41 | task, err := client.Tasks.Wait(task.ID) 42 | taskErr, ok := err.(TaskError) 43 | Expect(ok).ShouldNot(BeNil()) 44 | Expect(taskErr.ID).Should(Equal(task.ID)) 45 | }) 46 | 47 | It("TaskTimeoutError", func() { 48 | // Unit test only 49 | if isIntegrationTest() { 50 | return 51 | } 52 | client.options.TaskPollTimeout = 1 * time.Second 53 | task := &Task{ID: "fake-id", State: "QUEUED", Operation: "fake-op"} 54 | server.SetResponseJson(200, task) 55 | task, err := client.Tasks.Wait(task.ID) 56 | taskErr, ok := err.(TaskTimeoutError) 57 | Expect(ok).ShouldNot(BeNil()) 58 | Expect(taskErr.ID).Should(Equal(task.ID)) 59 | }) 60 | 61 | It("HttpError", func() { 62 | // Unit test only 63 | if isIntegrationTest() { 64 | return 65 | } 66 | client.options.TaskPollTimeout = 1 * time.Second 67 | task := &Task{ID: "fake-id", State: "QUEUED", Operation: "fake-op"} 68 | server.SetResponseJson(500, "server error") 69 | task, err := client.Tasks.Wait(task.ID) 70 | taskErr, ok := err.(HttpError) 71 | Expect(ok).ShouldNot(BeNil()) 72 | Expect(taskErr.StatusCode).Should(Equal(500)) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /photon/datastores_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = Describe("Datastores", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | datastoreSpec *Datastore 23 | ) 24 | 25 | BeforeEach(func() { 26 | server, client = testSetup() 27 | 28 | datastoreId := "1234" 29 | datastoreSpec = &Datastore{ 30 | Kind: "datastore", 31 | Type: "LOCAL_VMFS", 32 | ID: datastoreId, 33 | SelfLink: "https://192.0.0.2" + rootUrl + "/infrastructure/datastores/" + datastoreId, 34 | } 35 | }) 36 | 37 | AfterEach(func() { 38 | server.Close() 39 | }) 40 | 41 | Describe("Get", func() { 42 | It("Get a single datastore successfully", func() { 43 | 44 | server.SetResponseJson(200, datastoreSpec) 45 | 46 | datastore, err := client.Datastores.Get(datastoreSpec.ID) 47 | GinkgoT().Log(err) 48 | 49 | Expect(err).Should(BeNil()) 50 | Expect(datastore).ShouldNot(BeNil()) 51 | Expect(datastore.Kind).Should(Equal(datastoreSpec.Kind)) 52 | Expect(datastore.Type).Should(Equal(datastoreSpec.Type)) 53 | Expect(datastore.ID).Should(Equal(datastoreSpec.ID)) 54 | 55 | }) 56 | }) 57 | Describe("GetAll", func() { 58 | It("Get all datastores successfully", func() { 59 | datastoresExpected := Datastores{ 60 | Items: []Datastore{*datastoreSpec}, 61 | } 62 | 63 | server.SetResponseJson(200, datastoresExpected) 64 | 65 | datastores, err := client.Datastores.GetAll() 66 | GinkgoT().Log(err) 67 | 68 | Expect(err).Should(BeNil()) 69 | Expect(datastores).ShouldNot(BeNil()) 70 | Expect(datastores.Items[0].Kind).Should(Equal(datastoreSpec.Kind)) 71 | Expect(datastores.Items[0].Type).Should(Equal(datastoreSpec.Type)) 72 | Expect(datastores.Items[0].ID).Should(Equal(datastoreSpec.ID)) 73 | }) 74 | }) 75 | }) 76 | -------------------------------------------------------------------------------- /photon/hosts.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for hosts API. 18 | type HostsAPI struct { 19 | client *Client 20 | } 21 | 22 | var hostUrl string = rootUrl + "/infrastructure/hosts" 23 | 24 | // Sets host's availability zone. 25 | func (api *HostsAPI) SetAvailabilityZone(id string, availabilityZone *HostSetAvailabilityZoneOperation) (task *Task, err error) { 26 | body, err := json.Marshal(availabilityZone) 27 | if err != nil { 28 | return 29 | } 30 | 31 | res, err := api.client.restClient.Post( 32 | api.client.Endpoint+hostUrl+"/"+id+"/set_availability_zone", 33 | "application/json", 34 | bytes.NewReader(body), 35 | api.client.options.TokenOptions) 36 | 37 | if err != nil { 38 | return 39 | } 40 | 41 | defer res.Body.Close() 42 | task, err = getTask(getError(res)) 43 | return 44 | } 45 | 46 | // Gets all tasks with the specified host ID, using options to filter the results. 47 | // If options is nil, no filtering will occur. 48 | func (api *HostsAPI) GetTasks(id string, options *TaskGetOptions) (result *TaskList, err error) { 49 | uri := api.client.Endpoint + hostUrl + "/" + id + "/tasks" 50 | if options != nil { 51 | uri += getQueryString(options) 52 | } 53 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 54 | if err != nil { 55 | return 56 | } 57 | 58 | result = &TaskList{} 59 | err = json.Unmarshal(res, result) 60 | return 61 | } 62 | 63 | // provision the host with the specified id 64 | func (api *HostsAPI) Provision(id string) (task *Task, err error) { 65 | body := []byte{} 66 | res, err := api.client.restClient.Post( 67 | api.client.Endpoint+hostUrl+"/"+id+"/provision", 68 | "application/json", 69 | bytes.NewReader(body), 70 | api.client.options.TokenOptions) 71 | if err != nil { 72 | return 73 | } 74 | defer res.Body.Close() 75 | task, err = getTask(getError(res)) 76 | return 77 | } 78 | -------------------------------------------------------------------------------- /photon/infrastructure_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = Describe("Infra", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | ) 23 | 24 | BeforeEach(func() { 25 | if isIntegrationTest() { 26 | Skip("Skipping deployment test on integration mode. Need undeployed environment") 27 | } 28 | server, client = testSetup() 29 | }) 30 | 31 | AfterEach(func() { 32 | server.Close() 33 | }) 34 | 35 | Describe("SyncHostsConfig", func() { 36 | It("Sync Hosts Config succeeds", func() { 37 | mockTask := createMockTask("SYNC_HOSTS_CONFIG", "COMPLETED") 38 | server.SetResponseJson(200, mockTask) 39 | 40 | task, err := client.Infra.SyncHostsConfig() 41 | task, err = client.Tasks.Wait(task.ID) 42 | 43 | GinkgoT().Log(err) 44 | Expect(err).Should(BeNil()) 45 | Expect(task).ShouldNot(BeNil()) 46 | Expect(task.Operation).Should(Equal("SYNC_HOSTS_CONFIG")) 47 | Expect(task.State).Should(Equal("COMPLETED")) 48 | }) 49 | }) 50 | 51 | Describe("SetImageDatastores", func() { 52 | It("Succeeds", func() { 53 | mockTask := createMockTask("UPDATE_IMAGE_DATASTORES", "COMPLETED") 54 | server.SetResponseJson(200, mockTask) 55 | 56 | imageDatastores := &ImageDatastores{ 57 | []string{"imageDatastore1", "imageDatastore2"}, 58 | } 59 | createdTask, err := client.Infra.SetImageDatastores(imageDatastores) 60 | createdTask, err = client.Tasks.Wait(createdTask.ID) 61 | 62 | Expect(err).Should(BeNil()) 63 | Expect(createdTask.Operation).Should(Equal("UPDATE_IMAGE_DATASTORES")) 64 | Expect(createdTask.State).Should(Equal("COMPLETED")) 65 | }) 66 | 67 | It("Fails", func() { 68 | mockApiError := createMockApiError("INVALID_IMAGE_DATASTORES", "Not a super set", 400) 69 | server.SetResponseJson(400, mockApiError) 70 | 71 | imageDatastores := &ImageDatastores{ 72 | []string{"imageDatastore1", "imageDatastore2"}, 73 | } 74 | createdTask, err := client.Infra.SetImageDatastores(imageDatastores) 75 | 76 | Expect(err).Should(Equal(*mockApiError)) 77 | Expect(createdTask).Should(BeNil()) 78 | }) 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /photon/zones.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for zones API. 18 | type ZonesAPI struct { 19 | client *Client 20 | } 21 | 22 | var zoneUrl string = rootUrl + "/zones" 23 | 24 | // Creates zone. 25 | func (api *ZonesAPI) Create(zoneSpec *ZoneCreateSpec) (task *Task, err error) { 26 | body, err := json.Marshal(zoneSpec) 27 | if err != nil { 28 | return 29 | } 30 | res, err := api.client.restClient.Post( 31 | api.client.Endpoint+zoneUrl, 32 | "application/json", 33 | bytes.NewReader(body), 34 | api.client.options.TokenOptions) 35 | if err != nil { 36 | return 37 | } 38 | defer res.Body.Close() 39 | task, err = getTask(getError(res)) 40 | return 41 | } 42 | 43 | // Gets zone with the specified ID. 44 | func (api *ZonesAPI) Get(id string) (zone *Zone, err error) { 45 | res, err := api.client.restClient.Get(api.getEntityUrl(id), api.client.options.TokenOptions) 46 | if err != nil { 47 | return 48 | } 49 | defer res.Body.Close() 50 | res, err = getError(res) 51 | if err != nil { 52 | return 53 | } 54 | zone = &Zone{} 55 | err = json.NewDecoder(res.Body).Decode(zone) 56 | return 57 | } 58 | 59 | // Returns all zones on an photon instance. 60 | func (api *ZonesAPI) GetAll() (result *Zones, err error) { 61 | uri := api.client.Endpoint + zoneUrl 62 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 63 | if err != nil { 64 | return 65 | } 66 | 67 | result = &Zones{} 68 | err = json.Unmarshal(res, result) 69 | return 70 | } 71 | 72 | // Deletes the zone with specified ID. 73 | func (api *ZonesAPI) Delete(id string) (task *Task, err error) { 74 | res, err := api.client.restClient.Delete(api.client.Endpoint+zoneUrl+"/"+id, api.client.options.TokenOptions) 75 | if err != nil { 76 | return 77 | } 78 | defer res.Body.Close() 79 | task, err = getTask(getError(res)) 80 | return 81 | } 82 | 83 | // Gets all tasks with the specified zone ID, using options to filter the results. 84 | // If options is nil, no filtering will occur. 85 | func (api *ZonesAPI) GetTasks(id string, options *TaskGetOptions) (result *TaskList, err error) { 86 | uri := api.client.Endpoint + zoneUrl + "/" + id + "/tasks" 87 | if options != nil { 88 | uri += getQueryString(options) 89 | } 90 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 91 | if err != nil { 92 | return 93 | } 94 | 95 | result = &TaskList{} 96 | err = json.Unmarshal(res, result) 97 | return 98 | } 99 | 100 | func (api *ZonesAPI) getEntityUrl(id string) (url string) { 101 | return api.client.Endpoint + zoneUrl + "/" + id 102 | } 103 | -------------------------------------------------------------------------------- /photon/routers.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for routers API. 18 | type RoutersAPI struct { 19 | client *Client 20 | } 21 | 22 | var routerUrl string = rootUrl + "/routers/" 23 | 24 | // Gets a router with the specified ID. 25 | func (api *RoutersAPI) Get(id string) (router *Router, err error) { 26 | res, err := api.client.restClient.Get(api.client.Endpoint+routerUrl+id, api.client.options.TokenOptions) 27 | if err != nil { 28 | return 29 | } 30 | defer res.Body.Close() 31 | res, err = getError(res) 32 | if err != nil { 33 | return 34 | } 35 | var result Router 36 | err = json.NewDecoder(res.Body).Decode(&result) 37 | return &result, err 38 | } 39 | 40 | // Updates router's attributes. 41 | func (api *RoutersAPI) UpdateRouter(id string, routerSpec *RouterUpdateSpec) (task *Task, err error) { 42 | body, err := json.Marshal(routerSpec) 43 | if err != nil { 44 | return 45 | } 46 | 47 | res, err := api.client.restClient.Patch( 48 | api.client.Endpoint+routerUrl+id, 49 | "application/json", 50 | bytes.NewReader(body), 51 | api.client.options.TokenOptions) 52 | if err != nil { 53 | return 54 | } 55 | 56 | defer res.Body.Close() 57 | task, err = getTask(getError(res)) 58 | return 59 | } 60 | 61 | // Deletes a router with specified ID. 62 | func (api *RoutersAPI) Delete(routerID string) (task *Task, err error) { 63 | res, err := api.client.restClient.Delete(api.client.Endpoint+routerUrl+routerID, api.client.options.TokenOptions) 64 | if err != nil { 65 | return 66 | } 67 | 68 | defer res.Body.Close() 69 | task, err = getTask(getError(res)) 70 | return 71 | } 72 | 73 | // Creates a subnet on the specified router. 74 | func (api *RoutersAPI) CreateSubnet(routerID string, spec *SubnetCreateSpec) (task *Task, err error) { 75 | body, err := json.Marshal(spec) 76 | if err != nil { 77 | return 78 | } 79 | res, err := api.client.restClient.Post( 80 | api.client.Endpoint+routerUrl+routerID+"/subnets", 81 | "application/json", 82 | bytes.NewReader(body), 83 | api.client.options.TokenOptions) 84 | if err != nil { 85 | return 86 | } 87 | defer res.Body.Close() 88 | task, err = getTask(getError(res)) 89 | return 90 | } 91 | 92 | // Gets subnets for router with the specified ID, using options to filter the results. 93 | // If options is nil, no filtering will occur. 94 | func (api *RoutersAPI) GetSubnets(routerID string, options *SubnetGetOptions) (result *Subnets, err error) { 95 | uri := api.client.Endpoint + routerUrl + routerID + "/subnets" 96 | if options != nil { 97 | uri += getQueryString(options) 98 | } 99 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 100 | if err != nil { 101 | return 102 | } 103 | 104 | result = &Subnets{} 105 | err = json.Unmarshal(res, result) 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /photon/networks.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for networks API. 18 | type NetworksAPI struct { 19 | client *Client 20 | } 21 | 22 | var networkUrl string = rootUrl + "/networks/" 23 | 24 | // Gets a network with the specified ID. 25 | func (api *NetworksAPI) Get(id string) (network *Network, err error) { 26 | res, err := api.client.restClient.Get(api.client.Endpoint+networkUrl+id, api.client.options.TokenOptions) 27 | if err != nil { 28 | return 29 | } 30 | defer res.Body.Close() 31 | res, err = getError(res) 32 | if err != nil { 33 | return 34 | } 35 | var result Network 36 | err = json.NewDecoder(res.Body).Decode(&result) 37 | return &result, err 38 | } 39 | 40 | // Updates network's attributes. 41 | func (api *NetworksAPI) UpdateNetwork(id string, networkSpec *NetworkUpdateSpec) (task *Task, err error) { 42 | body, err := json.Marshal(networkSpec) 43 | if err != nil { 44 | return 45 | } 46 | 47 | res, err := api.client.restClient.Patch( 48 | api.client.Endpoint+networkUrl+id, 49 | "application/json", 50 | bytes.NewReader(body), 51 | api.client.options.TokenOptions) 52 | if err != nil { 53 | return 54 | } 55 | 56 | defer res.Body.Close() 57 | task, err = getTask(getError(res)) 58 | return 59 | } 60 | 61 | // Deletes a network with specified ID. 62 | func (api *NetworksAPI) Delete(networkID string) (task *Task, err error) { 63 | res, err := api.client.restClient.Delete(api.client.Endpoint+networkUrl+networkID, api.client.options.TokenOptions) 64 | if err != nil { 65 | return 66 | } 67 | 68 | defer res.Body.Close() 69 | task, err = getTask(getError(res)) 70 | return 71 | } 72 | 73 | // Creates a subnet on the specified network. 74 | func (api *NetworksAPI) CreateSubnet(networkID string, spec *SubnetCreateSpec) (task *Task, err error) { 75 | body, err := json.Marshal(spec) 76 | if err != nil { 77 | return 78 | } 79 | res, err := api.client.restClient.Post( 80 | api.client.Endpoint+networkUrl+networkID+"/subnets", 81 | "application/json", 82 | bytes.NewReader(body), 83 | api.client.options.TokenOptions) 84 | if err != nil { 85 | return 86 | } 87 | defer res.Body.Close() 88 | task, err = getTask(getError(res)) 89 | return 90 | } 91 | 92 | // Gets subnets for network with the specified ID, using options to filter the results. 93 | // If options is nil, no filtering will occur. 94 | func (api *NetworksAPI) GetSubnets(networkID string, options *SubnetGetOptions) (result *Subnets, err error) { 95 | uri := api.client.Endpoint + networkUrl + networkID + "/subnets" 96 | if options != nil { 97 | uri += getQueryString(options) 98 | } 99 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 100 | if err != nil { 101 | return 102 | } 103 | 104 | result = &Subnets{} 105 | err = json.Unmarshal(res, result) 106 | return 107 | } 108 | -------------------------------------------------------------------------------- /photon/tasks.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "encoding/json" 14 | "time" 15 | ) 16 | 17 | // Contains functionality for tasks API. 18 | type TasksAPI struct { 19 | client *Client 20 | } 21 | 22 | var taskUrl string = rootUrl + "/tasks" 23 | 24 | // Gets a task by ID. 25 | func (api *TasksAPI) Get(id string) (task *Task, err error) { 26 | res, err := api.client.restClient.Get(api.client.Endpoint+taskUrl+"/"+id, api.client.options.TokenOptions) 27 | if err != nil { 28 | return 29 | } 30 | defer res.Body.Close() 31 | result, err := getTask(getError(res)) 32 | return result, err 33 | } 34 | 35 | // Gets all tasks, using options to filter the results. 36 | // If options is nil, no filtering will occur. 37 | func (api *TasksAPI) GetAll(options *TaskGetOptions) (result *TaskList, err error) { 38 | uri := api.client.Endpoint + taskUrl 39 | if options != nil { 40 | uri += getQueryString(options) 41 | } 42 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 43 | if err != nil { 44 | return 45 | } 46 | 47 | result = &TaskList{} 48 | err = json.Unmarshal(res, result) 49 | 50 | return 51 | } 52 | 53 | // Waits for a task to complete by polling the tasks API until a task returns with 54 | // the state COMPLETED or ERROR. Will wait no longer than the duration specified by timeout. 55 | func (api *TasksAPI) WaitTimeout(id string, timeout time.Duration) (task *Task, err error) { 56 | start := time.Now() 57 | numErrors := 0 58 | maxErrors := api.client.options.TaskRetryCount 59 | 60 | for time.Since(start) < timeout { 61 | task, err = api.Get(id) 62 | if err != nil { 63 | switch err.(type) { 64 | // If an ApiError comes back, something is wrong, return the error to the caller 65 | case ApiError: 66 | return 67 | // For other errors, retry before giving up 68 | default: 69 | numErrors++ 70 | if numErrors > maxErrors { 71 | return 72 | } 73 | } 74 | } else { 75 | // Reset the error count any time a successful call is made 76 | numErrors = 0 77 | if task.State == "COMPLETED" { 78 | return 79 | } 80 | if task.State == "ERROR" { 81 | err = TaskError{task.ID, getFailedStep(task)} 82 | return 83 | } 84 | } 85 | time.Sleep(api.client.options.TaskPollDelay) 86 | } 87 | err = TaskTimeoutError{id} 88 | return 89 | } 90 | 91 | // Waits for a task to complete by polling the tasks API until a task returns with 92 | // the state COMPLETED or ERROR. 93 | func (api *TasksAPI) Wait(id string) (task *Task, err error) { 94 | return api.WaitTimeout(id, api.client.options.TaskPollTimeout) 95 | } 96 | 97 | // Gets the failed step in the task to get error details for failed task. 98 | func getFailedStep(task *Task) (step Step) { 99 | var errorStep Step 100 | for _, s := range task.Steps { 101 | if s.State == "ERROR" { 102 | errorStep = s 103 | break 104 | } 105 | } 106 | 107 | return errorStep 108 | } 109 | -------------------------------------------------------------------------------- /photon/flavors.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for flavors API. 18 | type FlavorsAPI struct { 19 | client *Client 20 | } 21 | 22 | // Options used for find/get APIs 23 | type FlavorGetOptions struct { 24 | Name string `urlParam:"name"` 25 | Kind string `urlParam:"kind"` 26 | } 27 | 28 | var flavorUrl string = rootUrl + "/flavors" 29 | 30 | // Creates a flavor. 31 | func (api *FlavorsAPI) Create(spec *FlavorCreateSpec) (task *Task, err error) { 32 | body, err := json.Marshal(spec) 33 | if err != nil { 34 | return 35 | } 36 | res, err := api.client.restClient.Post( 37 | api.client.Endpoint+flavorUrl, 38 | "application/json", 39 | bytes.NewReader(body), 40 | api.client.options.TokenOptions) 41 | if err != nil { 42 | return 43 | } 44 | defer res.Body.Close() 45 | task, err = getTask(getError(res)) 46 | return 47 | } 48 | 49 | // Gets details of flavor with specified ID. 50 | func (api *FlavorsAPI) Get(flavorID string) (flavor *Flavor, err error) { 51 | res, err := api.client.restClient.Get(api.client.Endpoint+flavorUrl+"/"+flavorID, api.client.options.TokenOptions) 52 | if err != nil { 53 | return 54 | } 55 | defer res.Body.Close() 56 | res, err = getError(res) 57 | if err != nil { 58 | return 59 | } 60 | flavor = &Flavor{} 61 | err = json.NewDecoder(res.Body).Decode(flavor) 62 | return 63 | } 64 | 65 | // Gets flavors using options to filter results. Returns all flavors if options is nil. 66 | func (api *FlavorsAPI) GetAll(options *FlavorGetOptions) (flavors *FlavorList, err error) { 67 | uri := api.client.Endpoint + flavorUrl 68 | if options != nil { 69 | uri += getQueryString(options) 70 | } 71 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 72 | if err != nil { 73 | return 74 | } 75 | 76 | flavors = &FlavorList{} 77 | err = json.Unmarshal(res, flavors) 78 | return 79 | } 80 | 81 | // Deletes flavor with specified ID. 82 | func (api *FlavorsAPI) Delete(flavorID string) (task *Task, err error) { 83 | res, err := api.client.restClient.Delete(api.client.Endpoint+flavorUrl+"/"+flavorID, api.client.options.TokenOptions) 84 | if err != nil { 85 | return 86 | } 87 | defer res.Body.Close() 88 | task, err = getTask(getError(res)) 89 | return 90 | } 91 | 92 | // Gets all tasks with the specified flavor ID, using options to filter the results. 93 | // If options is nil, no filtering will occur. 94 | func (api *FlavorsAPI) GetTasks(id string, options *TaskGetOptions) (result *TaskList, err error) { 95 | uri := api.client.Endpoint + flavorUrl + "/" + id + "/tasks" 96 | if options != nil { 97 | uri += getQueryString(options) 98 | } 99 | 100 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 101 | if err != nil { 102 | return 103 | } 104 | 105 | result = &TaskList{} 106 | err = json.Unmarshal(res, result) 107 | return 108 | } 109 | -------------------------------------------------------------------------------- /photon/internal/mocks/httpserver.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package mocks 11 | 12 | import ( 13 | "encoding/json" 14 | "fmt" 15 | "net/http" 16 | "net/http/httptest" 17 | "net/url" 18 | "strconv" 19 | "strings" 20 | ) 21 | 22 | type ServerResponseData struct { 23 | StatuCode *int 24 | Body *string 25 | } 26 | 27 | type Server struct { 28 | HttpServer *httptest.Server 29 | DefaultResponse *ServerResponseData 30 | Responses map[string]*ServerResponseData 31 | } 32 | 33 | func newUnstartedTestServer() (server *Server) { 34 | status := 200 35 | body := "" 36 | 37 | server = &Server{ 38 | nil, 39 | &ServerResponseData{&status, &body}, 40 | make(map[string]*ServerResponseData), 41 | } 42 | 43 | server.HttpServer = httptest.NewUnstartedServer( 44 | http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 45 | w.Header().Set("Content-Type", "application/json") 46 | 47 | var response *ServerResponseData 48 | for k, v := range server.Responses { 49 | if strings.HasPrefix(r.URL.Path, k) { 50 | response = v 51 | break 52 | } 53 | } 54 | 55 | if response == nil { 56 | response = server.DefaultResponse 57 | } 58 | 59 | w.WriteHeader(*response.StatuCode) 60 | fmt.Fprintln(w, *response.Body) 61 | })) 62 | return 63 | } 64 | 65 | func NewTestServer() (server *Server) { 66 | server = newUnstartedTestServer() 67 | server.HttpServer.Start() 68 | return 69 | } 70 | 71 | func NewTlsTestServer() (server *Server) { 72 | server = newUnstartedTestServer() 73 | server.HttpServer.StartTLS() 74 | return 75 | } 76 | 77 | func (s *Server) Close() { 78 | if s.HttpServer != nil { 79 | s.HttpServer.Close() 80 | } 81 | } 82 | 83 | func (s *Server) SetResponse(status int, body string) { 84 | s.DefaultResponse = &ServerResponseData{StatuCode: &status, Body: &body} 85 | } 86 | 87 | func (s *Server) SetResponseJson(status int, v interface{}) { 88 | s.SetResponse(status, s.toJson(v)) 89 | } 90 | 91 | func (s *Server) SetResponseForPath(path string, status int, body string) { 92 | s.Responses[path] = &ServerResponseData{&status, &body} 93 | } 94 | 95 | func (s *Server) SetResponseJsonForPath(path string, status int, v interface{}) { 96 | s.SetResponseForPath(path, status, s.toJson(v)) 97 | } 98 | 99 | func (s *Server) GetAddressAndPort() (address string, port int, err error) { 100 | serverURL, err := url.Parse(s.HttpServer.URL) 101 | if err != nil { 102 | return 103 | } 104 | 105 | hostList := strings.Split(serverURL.Host, ":") 106 | address = hostList[0] 107 | port, err = strconv.Atoi(hostList[1]) 108 | if err != nil { 109 | return 110 | } 111 | 112 | return 113 | } 114 | 115 | func (s *Server) toJson(v interface{}) string { 116 | res, err := json.Marshal(v) 117 | if err != nil { 118 | // Since this method is only for testing, don't return 119 | // any errors, just panic. 120 | panic("Error serializing struct into JSON") 121 | } 122 | // json.Marshal returns []byte, convert to string 123 | return string(res[:]) 124 | } 125 | -------------------------------------------------------------------------------- /photon/util.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | "fmt" 16 | "io/ioutil" 17 | "net/http" 18 | "net/url" 19 | "reflect" 20 | ) 21 | 22 | // Reads an error out of the HTTP response, or does nothing if 23 | // no error occurred. 24 | func getError(res *http.Response) (*http.Response, error) { 25 | // Do nothing if the response is a successful 2xx 26 | if res.StatusCode/100 == 2 { 27 | return res, nil 28 | } 29 | var apiError ApiError 30 | // ReadAll is usually a bad practice, but here we need to read the response all 31 | // at once because we may attempt to use the data twice. It's preferable to use 32 | // methods that take io.Reader, e.g. json.NewDecoder 33 | body, err := ioutil.ReadAll(res.Body) 34 | if err != nil { 35 | return nil, err 36 | } 37 | err = json.Unmarshal(body, &apiError) 38 | if err != nil { 39 | // If deserializing into ApiError fails, return a generic HttpError instead 40 | return nil, HttpError{res.StatusCode, string(body[:])} 41 | } 42 | apiError.HttpStatusCode = res.StatusCode 43 | return nil, apiError 44 | } 45 | 46 | // Reads a task object out of the HTTP response. Takes an error argument 47 | // so that GetTask can easily wrap GetError. This function will do nothing 48 | // if e is not nil. 49 | // e.g. res, err := getTask(getError(someApi.Get())) 50 | func getTask(res *http.Response, e error) (*Task, error) { 51 | if e != nil { 52 | return nil, e 53 | } 54 | var task Task 55 | err := json.NewDecoder(res.Body).Decode(&task) 56 | if err != nil { 57 | return nil, err 58 | } 59 | if task.State == "ERROR" { 60 | // Critical: return task as well, so that it can be examined 61 | // for error details. 62 | return &task, TaskError{task.ID, getFailedStep(&task)} 63 | } 64 | return &task, nil 65 | } 66 | 67 | // Converts an options struct into a query string. 68 | // E.g. type Foo struct {A int; B int} might return "?a=5&b=10". 69 | // Will return an empty string if no options are set. 70 | func getQueryString(options interface{}) string { 71 | buffer := bytes.Buffer{} 72 | buffer.WriteString("?") 73 | strct := reflect.ValueOf(options).Elem() 74 | typ := strct.Type() 75 | for i := 0; i < strct.NumField(); i++ { 76 | field := strct.Field(i) 77 | value := fmt.Sprint(field.Interface()) 78 | if value != "" { 79 | buffer.WriteString(typ.Field(i).Tag.Get("urlParam") + "=" + url.QueryEscape(value)) 80 | if i < strct.NumField()-1 { 81 | buffer.WriteString("&") 82 | } 83 | } 84 | } 85 | uri := buffer.String() 86 | if uri == "?" { 87 | return "" 88 | } 89 | return uri 90 | } 91 | 92 | // Sets security groups for a given entity (deployment/tenant/project) 93 | func setSecurityGroups(client *Client, entityUrl string, securityGroups *SecurityGroupsSpec) (task *Task, err error) { 94 | body, err := json.Marshal(securityGroups) 95 | if err != nil { 96 | return 97 | } 98 | url := entityUrl + "/set_security_groups" 99 | res, err := client.restClient.Post( 100 | url, 101 | "application/json", 102 | bytes.NewReader(body), 103 | client.options.TokenOptions) 104 | if err != nil { 105 | return 106 | } 107 | defer res.Body.Close() 108 | task, err = getTask(getError(res)) 109 | return 110 | } 111 | -------------------------------------------------------------------------------- /photon/subnets.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for subnets API. 18 | type SubnetsAPI struct { 19 | client *Client 20 | } 21 | 22 | // Options for GetSubnets API. 23 | type SubnetGetOptions struct { 24 | Name string `urlParam:"name"` 25 | } 26 | 27 | var subnetUrl string = rootUrl + "/subnets" 28 | 29 | // Creates a portgroup. 30 | func (api *SubnetsAPI) Create(subnetSpec *SubnetCreateSpec) (task *Task, err error) { 31 | body, err := json.Marshal(subnetSpec) 32 | if err != nil { 33 | return 34 | } 35 | res, err := api.client.restClient.Post( 36 | api.client.Endpoint+subnetUrl, 37 | "application/json", 38 | bytes.NewReader(body), 39 | api.client.options.TokenOptions) 40 | if err != nil { 41 | return 42 | } 43 | defer res.Body.Close() 44 | task, err = getTask(getError(res)) 45 | return 46 | } 47 | 48 | // Deletes a subnet with the specified ID. 49 | func (api *SubnetsAPI) Delete(id string) (task *Task, err error) { 50 | res, err := api.client.restClient.Delete(api.client.Endpoint+subnetUrl+"/"+id, api.client.options.TokenOptions) 51 | if err != nil { 52 | return 53 | } 54 | 55 | defer res.Body.Close() 56 | 57 | task, err = getTask(getError(res)) 58 | return 59 | } 60 | 61 | // Gets a subnet with the specified ID. 62 | func (api *SubnetsAPI) Get(id string) (subnet *Subnet, err error) { 63 | res, err := api.client.restClient.Get(api.client.Endpoint+subnetUrl+"/"+id, api.client.options.TokenOptions) 64 | if err != nil { 65 | return 66 | } 67 | defer res.Body.Close() 68 | res, err = getError(res) 69 | if err != nil { 70 | return 71 | } 72 | var result Subnet 73 | err = json.NewDecoder(res.Body).Decode(&result) 74 | return &result, err 75 | } 76 | 77 | // Updates subnet's attributes. 78 | func (api *SubnetsAPI) Update(id string, subnetSpec *SubnetUpdateSpec) (task *Task, err error) { 79 | body, err := json.Marshal(subnetSpec) 80 | if err != nil { 81 | return 82 | } 83 | 84 | res, err := api.client.restClient.Patch( 85 | api.client.Endpoint+subnetUrl+"/"+id, 86 | "application/json", 87 | bytes.NewReader(body), 88 | api.client.options.TokenOptions) 89 | if err != nil { 90 | return 91 | } 92 | 93 | defer res.Body.Close() 94 | task, err = getTask(getError(res)) 95 | return 96 | } 97 | 98 | // Returns all subnets 99 | func (api *SubnetsAPI) GetAll(options *SubnetGetOptions) (result *Subnets, err error) { 100 | uri := api.client.Endpoint + subnetUrl 101 | if options != nil { 102 | uri += getQueryString(options) 103 | } 104 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 105 | if err != nil { 106 | return 107 | } 108 | 109 | result = &Subnets{} 110 | err = json.Unmarshal(res, result) 111 | return 112 | } 113 | 114 | // Sets default subnet. 115 | func (api *SubnetsAPI) SetDefault(id string) (task *Task, err error) { 116 | res, err := api.client.restClient.Post( 117 | api.client.Endpoint+subnetUrl+"/"+id+"/set_default", 118 | "application/json", 119 | bytes.NewReader([]byte("")), 120 | api.client.options.TokenOptions) 121 | if err != nil { 122 | return 123 | } 124 | defer res.Body.Close() 125 | task, err = getTask(getError(res)) 126 | return 127 | } 128 | -------------------------------------------------------------------------------- /photon/disks.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for disks API. 18 | type DisksAPI struct { 19 | client *Client 20 | } 21 | 22 | var diskUrl string = rootUrl + "/disks/" 23 | 24 | // Gets a PersistentDisk for the disk with specified ID. 25 | func (api *DisksAPI) Get(diskID string) (disk *PersistentDisk, err error) { 26 | res, err := api.client.restClient.Get(api.client.Endpoint+diskUrl+diskID, api.client.options.TokenOptions) 27 | if err != nil { 28 | return 29 | } 30 | defer res.Body.Close() 31 | res, err = getError(res) 32 | if err != nil { 33 | return 34 | } 35 | disk = &PersistentDisk{} 36 | err = json.NewDecoder(res.Body).Decode(disk) 37 | return 38 | } 39 | 40 | // Deletes a disk with the specified ID. 41 | func (api *DisksAPI) Delete(diskID string) (task *Task, err error) { 42 | res, err := api.client.restClient.Delete(api.client.Endpoint+diskUrl+diskID, api.client.options.TokenOptions) 43 | if err != nil { 44 | return 45 | } 46 | defer res.Body.Close() 47 | task, err = getTask(getError(res)) 48 | return 49 | } 50 | 51 | // Gets all tasks with the specified disk ID, using options to filter the results. 52 | // If options is nil, no filtering will occur. 53 | func (api *DisksAPI) GetTasks(id string, options *TaskGetOptions) (result *TaskList, err error) { 54 | uri := api.client.Endpoint + diskUrl + id + "/tasks" 55 | if options != nil { 56 | uri += getQueryString(options) 57 | } 58 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 59 | if err != nil { 60 | return 61 | } 62 | 63 | result = &TaskList{} 64 | err = json.Unmarshal(res, result) 65 | return 66 | } 67 | 68 | // Gets IAM Policy on a disk. 69 | func (api *DisksAPI) GetIam(id string) (policy []*RoleBinding, err error) { 70 | res, err := api.client.restClient.Get( 71 | api.client.Endpoint+diskUrl+id+"/iam", 72 | api.client.options.TokenOptions) 73 | if err != nil { 74 | return 75 | } 76 | defer res.Body.Close() 77 | res, err = getError(res) 78 | if err != nil { 79 | return 80 | } 81 | err = json.NewDecoder(res.Body).Decode(&policy) 82 | return policy, err 83 | } 84 | 85 | // Sets IAM Policy on a disk. 86 | func (api *DisksAPI) SetIam(id string, policy []*RoleBinding) (task *Task, err error) { 87 | body, err := json.Marshal(policy) 88 | if err != nil { 89 | return 90 | } 91 | res, err := api.client.restClient.Post( 92 | api.client.Endpoint+diskUrl+id+"/iam", 93 | "application/json", 94 | bytes.NewReader(body), 95 | api.client.options.TokenOptions) 96 | if err != nil { 97 | return 98 | } 99 | defer res.Body.Close() 100 | task, err = getTask(getError(res)) 101 | return 102 | } 103 | 104 | // Modifies IAM Policy on a disk. 105 | func (api *DisksAPI) ModifyIam(id string, policyDelta []*RoleBindingDelta) (task *Task, err error) { 106 | body, err := json.Marshal(policyDelta) 107 | if err != nil { 108 | return 109 | } 110 | res, err := api.client.restClient.Patch( 111 | api.client.Endpoint+diskUrl+id+"/iam", 112 | "application/json", 113 | bytes.NewReader(body), 114 | api.client.options.TokenOptions) 115 | if err != nil { 116 | return 117 | } 118 | defer res.Body.Close() 119 | task, err = getTask(getError(res)) 120 | return 121 | } 122 | -------------------------------------------------------------------------------- /photon/auth_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "fmt" 14 | 15 | . "github.com/onsi/ginkgo" 16 | . "github.com/onsi/gomega" 17 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 18 | "github.com/vmware/photon-controller-go-sdk/photon/lightwave" 19 | ) 20 | 21 | var _ = Describe("Auth", func() { 22 | var ( 23 | server *mocks.Server 24 | authServer *mocks.Server 25 | client *Client 26 | ) 27 | 28 | BeforeEach(func() { 29 | if isIntegrationTest() { 30 | Skip("Skipping auth test as we don't know if auth is on or off.") 31 | } 32 | 33 | server, client = testSetup() 34 | authServer = mocks.NewTlsTestServer() 35 | }) 36 | 37 | AfterEach(func() { 38 | server.Close() 39 | authServer.Close() 40 | }) 41 | 42 | Describe("GetTokensByPassword", func() { 43 | Context("when auth is enabled", func() { 44 | BeforeEach(func() { 45 | server.SetResponseJson(200, createMockAuthInfo(authServer)) 46 | }) 47 | 48 | It("returns tokens", func() { 49 | expected := &TokenOptions{ 50 | AccessToken: "fake_access_token", 51 | ExpiresIn: 36000, 52 | RefreshToken: "fake_refresh_token", 53 | IdToken: "fake_id_token", 54 | TokenType: "Bearer", 55 | } 56 | authServer.SetResponseJson(200, expected) 57 | 58 | info, err := client.Auth.GetTokensByPassword("username", "password") 59 | fmt.Fprintf(GinkgoWriter, "Got tokens: %+v\n", info) 60 | Expect(err).Should(BeNil()) 61 | Expect(info).Should(BeEquivalentTo(expected)) 62 | }) 63 | }) 64 | }) 65 | 66 | Describe("GetClientTokensByPassword", func() { 67 | Context("when auth is enabled", func() { 68 | BeforeEach(func() { 69 | server.SetResponseJson(200, createMockAuthInfo(authServer)) 70 | }) 71 | 72 | It("returns tokens", func() { 73 | expected := &TokenOptions{ 74 | AccessToken: "fake_access_token", 75 | ExpiresIn: 36000, 76 | RefreshToken: "fake_refresh_token", 77 | IdToken: "fake_id_token", 78 | TokenType: "Bearer", 79 | } 80 | authServer.SetResponseJson(200, expected) 81 | 82 | info, err := client.Auth.GetClientTokensByPassword("username", "password", "client_id") 83 | fmt.Fprintf(GinkgoWriter, "Got tokens: %+v\n", info) 84 | Expect(err).Should(BeNil()) 85 | Expect(info).Should(BeEquivalentTo(expected)) 86 | }) 87 | }) 88 | }) 89 | 90 | Describe("GetTokensByRefreshToken", func() { 91 | Context("when auth is enabled", func() { 92 | BeforeEach(func() { 93 | server.SetResponseJson(200, createMockAuthInfo(authServer)) 94 | }) 95 | 96 | It("returns tokens", func() { 97 | expected := &TokenOptions{ 98 | AccessToken: "fake_access_token", 99 | ExpiresIn: 36000, 100 | IdToken: "fake_id_token", 101 | TokenType: "Bearer", 102 | } 103 | authServer.SetResponseJson(200, expected) 104 | 105 | info, err := client.Auth.GetTokensByRefreshToken("refresh_token") 106 | fmt.Fprintf(GinkgoWriter, "Got tokens: %+v\n", info) 107 | Expect(err).Should(BeNil()) 108 | Expect(info).Should(BeEquivalentTo(expected)) 109 | }) 110 | }) 111 | }) 112 | 113 | Describe("ParseTokenDetails", func() { 114 | Context("with the fake token", func() { 115 | BeforeEach(func() { 116 | server.SetResponseJson(200, createMockAuthInfo(authServer)) 117 | }) 118 | 119 | It("returns tokens", func() { 120 | expected := &lightwave.JWTToken{ 121 | Algorithm: "RS256", 122 | } 123 | authServer.SetResponseJson(200, expected) 124 | 125 | jwtToken, err := client.Auth.parseTokenDetails("eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJlYy1hZG1pbkBlc3hjbG91Z") 126 | fmt.Fprintf(GinkgoWriter, "Got token details: %+v\n", jwtToken) 127 | Expect(err).Should(BeNil()) 128 | Expect(jwtToken).Should(BeEquivalentTo(expected)) 129 | }) 130 | }) 131 | }) 132 | }) 133 | -------------------------------------------------------------------------------- /photon/lightwave/jwttoken_test.go: -------------------------------------------------------------------------------- 1 | package lightwave 2 | 3 | import ( 4 | . "github.com/onsi/ginkgo" 5 | . "github.com/onsi/gomega" 6 | ) 7 | 8 | var _ = Describe("JWTToken", func() { 9 | Describe("ParseTokenDetails", func() { 10 | Context("parsing token details", func() { 11 | It("parses tokens", func() { 12 | expected := &JWTToken{ 13 | TokenId: "LTW1jD-LccorfMJN-SUELdAwUKO8lHTHwGL2kGNVc5g", 14 | Algorithm: "RS256", 15 | Subject: "administrator@photon.com", 16 | Audience: []string{"administrator@photon.com", "rs_photon_platform"}, 17 | Groups: []string{"photon.com\\Users", "photon.com\\Administrators", "photon.com\\CAAdmins", "photon.com\\Everyone"}, 18 | Issuer: "https://10.118.108.208/openidconnect/photon.com", 19 | IssuedAt: 1488478342, 20 | Expires: 1488478642, 21 | Scope: "rs_photon_platform at_groups openid offline_access", 22 | TokenType: "Bearer", 23 | TokenClass: "access_token", 24 | Tenant: "photon.com", 25 | } 26 | resp := ParseTokenDetails( 27 | "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbmlzdHJhdG9yQHBob3Rvbi5jb20iLCJhdWQiOlsiYWRta" + 28 | "W5pc3RyYXRvckBwaG90b24uY29tIiwicnNfcGhvdG9uX3BsYXRmb3JtIl0sInNjb3BlIjoicnNfcGhvdG9uX3BsYXRmb3J" + 29 | "tIGF0X2dyb3VwcyBvcGVuaWQgb2ZmbGluZV9hY2Nlc3MiLCJpc3MiOiJodHRwczpcL1wvMTAuMTE4LjEwOC4yMDhcL29wZ" + 30 | "W5pZGNvbm5lY3RcL3Bob3Rvbi5jb20iLCJncm91cHMiOlsicGhvdG9uLmNvbVxcVXNlcnMiLCJwaG90b24uY29tXFxBZG1" + 31 | "pbmlzdHJhdG9ycyIsInBob3Rvbi5jb21cXENBQWRtaW5zIiwicGhvdG9uLmNvbVxcRXZlcnlvbmUiXSwidG9rZW5fY2xhc" + 32 | "3MiOiJhY2Nlc3NfdG9rZW4iLCJ0b2tlbl90eXBlIjoiQmVhcmVyIiwiZXhwIjoxNDg4NDc4NjQyLCJpYXQiOjE0ODg0Nzg" + 33 | "zNDIsImp0aSI6IkxUVzFqRC1MY2NvcmZNSk4tU1VFTGRBd1VLTzhsSFRId0dMMmtHTlZjNWciLCJ0ZW5hbnQiOiJwaG90b" + 34 | "24uY29tIn0.UFiruuobguHiVZZHnhCxkqw8k98RS6y2A9Dh_7LOclhvXxthUfae0JZvLVN7sUmeVss-aDFkxTRWUVMmHaj" + 35 | "jDCERSI6oMBiWU2aFtcS0ZdJGEbOLbDNG2tOCyyIkI6IYaWmVEGCGjhn3bXGjxC5dvH4au0sYynxTjD97StqmaqoQ2OhWZ" + 36 | "075vdIWyybwJlSgVk8WCjszjuH_4oe87hvIn79QnF37WBXZua_dhaeiAOzm752LFGr3kRp6BYIfp_z-NHBFPTEL93d4Wx0" + 37 | "DOam7EUa65vOeoiRiLJjhjNsJ_nGhka_v9m5GMlhst_b1HqCUmLFmt6POFuQCf3UswNtEX7rcIfSlem5Z002TpzzrElPqP" + 38 | "oxGHrw3vWAUPjHwucJ7CIp9AmF1Xsh-TfybxS66THbObt3HxE6Zb3pCFEgsZegjUb7CUDzOaicWexDF6Ft5Xv_ppH4-NHH" + 39 | "fzdFlYvdrS0YATNtK4YjkacoAKYzdMH-F7usxDJjanS0b73BEXzBaTAzVCNPGflulyrE8j1iDcpazHWQMMq1NZ5_OBw7TF" + 40 | "xLv5Te854cWEVMbIDOkQShUGLDiN52TtNMfdqFP-4M2lOcrmkShG4QXKQrYnlTy-b3tsMsukoihpKsp-yaW-DPs9J1hvlD" + 41 | "wqbwm2H0GDj2tYC6X2EiVDofjJZ4YqpcUCoE") 42 | Expect(resp).To(BeEquivalentTo(expected)) 43 | }) 44 | }) 45 | 46 | Context("parsing raw token details", func() { 47 | It("parses raw tokens", func() { 48 | resp, err := ParseRawTokenDetails( 49 | "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJhZG1pbmlzdHJhdG9yQHBob3Rvbi5jb20iLCJhdWQiOlsiYWRta" + 50 | "W5pc3RyYXRvckBwaG90b24uY29tIiwicnNfcGhvdG9uX3BsYXRmb3JtIl0sInNjb3BlIjoicnNfcGhvdG9uX3BsYXRmb3J" + 51 | "tIGF0X2dyb3VwcyBvcGVuaWQgb2ZmbGluZV9hY2Nlc3MiLCJpc3MiOiJodHRwczpcL1wvMTAuMTE4LjEwOC4yMDhcL29wZ" + 52 | "W5pZGNvbm5lY3RcL3Bob3Rvbi5jb20iLCJncm91cHMiOlsicGhvdG9uLmNvbVxcVXNlcnMiLCJwaG90b24uY29tXFxBZG1" + 53 | "pbmlzdHJhdG9ycyIsInBob3Rvbi5jb21cXENBQWRtaW5zIiwicGhvdG9uLmNvbVxcRXZlcnlvbmUiXSwidG9rZW5fY2xhc" + 54 | "3MiOiJhY2Nlc3NfdG9rZW4iLCJ0b2tlbl90eXBlIjoiQmVhcmVyIiwiZXhwIjoxNDg4NDc4NjQyLCJpYXQiOjE0ODg0Nzg" + 55 | "zNDIsImp0aSI6IkxUVzFqRC1MY2NvcmZNSk4tU1VFTGRBd1VLTzhsSFRId0dMMmtHTlZjNWciLCJ0ZW5hbnQiOiJwaG90b" + 56 | "24uY29tIn0.UFiruuobguHiVZZHnhCxkqw8k98RS6y2A9Dh_7LOclhvXxthUfae0JZvLVN7sUmeVss-aDFkxTRWUVMmHaj" + 57 | "jDCERSI6oMBiWU2aFtcS0ZdJGEbOLbDNG2tOCyyIkI6IYaWmVEGCGjhn3bXGjxC5dvH4au0sYynxTjD97StqmaqoQ2OhWZ" + 58 | "075vdIWyybwJlSgVk8WCjszjuH_4oe87hvIn79QnF37WBXZua_dhaeiAOzm752LFGr3kRp6BYIfp_z-NHBFPTEL93d4Wx0" + 59 | "DOam7EUa65vOeoiRiLJjhjNsJ_nGhka_v9m5GMlhst_b1HqCUmLFmt6POFuQCf3UswNtEX7rcIfSlem5Z002TpzzrElPqP" + 60 | "oxGHrw3vWAUPjHwucJ7CIp9AmF1Xsh-TfybxS66THbObt3HxE6Zb3pCFEgsZegjUb7CUDzOaicWexDF6Ft5Xv_ppH4-NHH" + 61 | "fzdFlYvdrS0YATNtK4YjkacoAKYzdMH-F7usxDJjanS0b73BEXzBaTAzVCNPGflulyrE8j1iDcpazHWQMMq1NZ5_OBw7TF" + 62 | "xLv5Te854cWEVMbIDOkQShUGLDiN52TtNMfdqFP-4M2lOcrmkShG4QXKQrYnlTy-b3tsMsukoihpKsp-yaW-DPs9J1hvlD" + 63 | "wqbwm2H0GDj2tYC6X2EiVDofjJZ4YqpcUCoE") 64 | Expect(err).To(BeNil()) 65 | Expect(resp).ToNot(BeNil()) 66 | Expect(len(resp)).To(BeNumerically(">", 0)) 67 | }) 68 | }) 69 | }) 70 | }) 71 | -------------------------------------------------------------------------------- /photon/auth.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "fmt" 14 | "github.com/vmware/photon-controller-go-sdk/photon/lightwave" 15 | ) 16 | 17 | // Contains functionality for auth API. 18 | type AuthAPI struct { 19 | client *Client 20 | } 21 | 22 | // Gets Tokens from username/password. 23 | func (api *AuthAPI) GetTokensByPassword(username string, password string) (tokenOptions *TokenOptions, err error) { 24 | oidcClient, err := api.buildOIDCClient() 25 | if err != nil { 26 | return 27 | } 28 | 29 | tokenResponse, err := oidcClient.GetTokenByPasswordGrant(username, password) 30 | if err != nil { 31 | return 32 | } 33 | 34 | return api.toTokenOptions(tokenResponse), nil 35 | } 36 | 37 | // Gets tokens for client from username, password and a client ID. 38 | func (api *AuthAPI) GetClientTokensByPassword(username string, password string, clientID string) (tokenOptions *TokenOptions, err error) { 39 | oidcClient, err := api.buildOIDCClient() 40 | if err != nil { 41 | return 42 | } 43 | 44 | tokenResponse, err := oidcClient.GetClientTokenByPasswordGrant(username, password, clientID) 45 | if err != nil { 46 | return 47 | } 48 | 49 | return api.toTokenOptions(tokenResponse), nil 50 | } 51 | 52 | // GetTokensFromWindowsLogInContext gets tokens based on Windows logged in context 53 | // In case of running on platform other than Windows, it returns error 54 | func (api *AuthAPI) GetTokensFromWindowsLogInContext() (tokenOptions *TokenOptions, err error) { 55 | oidcClient, err := api.buildOIDCClient() 56 | if err != nil { 57 | return 58 | } 59 | 60 | tokenResponse, err := oidcClient.GetTokensFromWindowsLogInContext() 61 | if err != nil { 62 | return 63 | } 64 | 65 | return api.toTokenOptions(tokenResponse), nil 66 | } 67 | 68 | // Gets tokens from refresh token. 69 | func (api *AuthAPI) GetTokensByRefreshToken(refreshtoken string) (tokenOptions *TokenOptions, err error) { 70 | oidcClient, err := api.buildOIDCClient() 71 | if err != nil { 72 | return 73 | } 74 | 75 | tokenResponse, err := oidcClient.GetTokenByRefreshTokenGrant(refreshtoken) 76 | if err != nil { 77 | return 78 | } 79 | 80 | return api.toTokenOptions(tokenResponse), nil 81 | } 82 | 83 | func (api *AuthAPI) getAuthEndpoint() (endpoint string, err error) { 84 | authInfo, err := api.client.System.GetAuthInfo() 85 | if err != nil { 86 | return 87 | } 88 | 89 | if authInfo.Port == 0 { 90 | authInfo.Port = 443 91 | } 92 | 93 | return fmt.Sprintf("https://%s:%d", authInfo.Endpoint, authInfo.Port), nil 94 | } 95 | 96 | func (api *AuthAPI) buildOIDCClient() (client *lightwave.OIDCClient, err error) { 97 | authEndPoint, err := api.getAuthEndpoint() 98 | if err != nil { 99 | return 100 | } 101 | 102 | return lightwave.NewOIDCClient( 103 | authEndPoint, 104 | api.buildOIDCClientOptions(&api.client.options), 105 | api.client.restClient.logger), nil 106 | } 107 | 108 | const tokenScope string = "openid offline_access rs_photon_platform at_groups" 109 | 110 | func (api *AuthAPI) buildOIDCClientOptions(options *ClientOptions) *lightwave.OIDCClientOptions { 111 | return &lightwave.OIDCClientOptions{ 112 | IgnoreCertificate: api.client.options.IgnoreCertificate, 113 | RootCAs: api.client.options.RootCAs, 114 | TokenScope: tokenScope, 115 | } 116 | } 117 | 118 | func (api *AuthAPI) toTokenOptions(response *lightwave.OIDCTokenResponse) *TokenOptions { 119 | return &TokenOptions{ 120 | AccessToken: response.AccessToken, 121 | ExpiresIn: response.ExpiresIn, 122 | RefreshToken: response.RefreshToken, 123 | IdToken: response.IdToken, 124 | TokenType: response.TokenType, 125 | } 126 | } 127 | 128 | // Parse the given token details. 129 | func (api *AuthAPI) parseTokenDetails(token string) (jwtToken *lightwave.JWTToken, err error) { 130 | jwtToken = lightwave.ParseTokenDetails(token) 131 | return jwtToken, nil 132 | } 133 | 134 | // Parse the given token raw details. 135 | func (api *AuthAPI) parseRawTokenDetails(token string) (jwtToken []string, err error) { 136 | jwtToken, err = lightwave.ParseRawTokenDetails(token) 137 | return jwtToken, err 138 | } 139 | -------------------------------------------------------------------------------- /photon/services.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for services API. 18 | type ServicesAPI struct { 19 | client *Client 20 | } 21 | 22 | var serviceUrl = rootUrl + "/services/" 23 | 24 | // Extended Properties 25 | const ( 26 | ExtendedPropertyDNS = "dns" 27 | ExtendedPropertyGateway = "gateway" 28 | ExtendedPropertyNetMask = "netmask" 29 | ExtendedPropertyLoadBalancerIP = "load_balancer_ip" 30 | ExtendedPropertyNumberOfMasters = "number_of_masters" 31 | ExtendedPropertyMasterIPs = "master_ips" 32 | ExtendedPropertyMasterIP = "master_ip" 33 | ExtendedPropertyMasterIP2 = "master_ip2" 34 | ExtendedPropertyContainerNetwork = "container_network" 35 | ExtendedPropertyNumberOfETCDs = "number_of_etcds" 36 | ExtendedPropertyETCDIP1 = "etcd_ip1" 37 | ExtendedPropertyETCDIP2 = "etcd_ip2" 38 | ExtendedPropertyETCDIP3 = "etcd_ip3" 39 | ExtendedPropertySSHKey = "ssh_key" 40 | ExtendedPropertyRegistryCACert = "registry_ca_cert" 41 | ExtendedPropertyAdminPassword = "admin_password" 42 | ) 43 | 44 | // Deletes a service with specified ID. 45 | func (api *ServicesAPI) Delete(id string) (task *Task, err error) { 46 | res, err := api.client.restClient.Delete(api.client.Endpoint+serviceUrl+id, api.client.options.TokenOptions) 47 | if err != nil { 48 | return 49 | } 50 | defer res.Body.Close() 51 | task, err = getTask(getError(res)) 52 | return 53 | } 54 | 55 | // Gets a service with the specified ID. 56 | func (api *ServicesAPI) Get(id string) (service *Service, err error) { 57 | res, err := api.client.restClient.Get(api.client.Endpoint+serviceUrl+id, api.client.options.TokenOptions) 58 | if err != nil { 59 | return 60 | } 61 | defer res.Body.Close() 62 | res, err = getError(res) 63 | if err != nil { 64 | return 65 | } 66 | var result Service 67 | err = json.NewDecoder(res.Body).Decode(&result) 68 | return &result, err 69 | } 70 | 71 | // Gets vms for service with the specified ID. 72 | func (api *ServicesAPI) GetVMs(id string) (result *VMs, err error) { 73 | uri := api.client.Endpoint + serviceUrl + id + "/vms" 74 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 75 | if err != nil { 76 | return 77 | } 78 | 79 | result = &VMs{} 80 | err = json.Unmarshal(res, result) 81 | return 82 | } 83 | 84 | // Resize a service to specified count. 85 | func (api *ServicesAPI) Resize(id string, resize *ServiceResizeOperation) (task *Task, err error) { 86 | body, err := json.Marshal(resize) 87 | if err != nil { 88 | return 89 | } 90 | res, err := api.client.restClient.Post( 91 | api.client.Endpoint+serviceUrl+id+"/resize", 92 | "application/json", 93 | bytes.NewReader(body), 94 | api.client.options.TokenOptions) 95 | if err != nil { 96 | return 97 | } 98 | defer res.Body.Close() 99 | task, err = getTask(getError(res)) 100 | return 101 | } 102 | 103 | // Start a background process to recreate failed VMs in a service with the specified ID. 104 | func (api *ServicesAPI) TriggerMaintenance(id string) (task *Task, err error) { 105 | body := []byte{} 106 | res, err := api.client.restClient.Post( 107 | api.client.Endpoint+serviceUrl+id+"/trigger_maintenance", 108 | "application/json", 109 | bytes.NewReader(body), 110 | api.client.options.TokenOptions) 111 | if err != nil { 112 | return 113 | } 114 | defer res.Body.Close() 115 | task, err = getTask(getError(res)) 116 | return 117 | } 118 | 119 | // Change a service version to the specified image by destroying and recreating the VMs. 120 | func (api *ServicesAPI) ChangeVersion(id string, changeVersion *ServiceChangeVersionOperation) (task *Task, err error) { 121 | body, err := json.Marshal(changeVersion) 122 | if err != nil { 123 | return 124 | } 125 | res, err := api.client.restClient.Post( 126 | api.client.Endpoint+serviceUrl+id+"/change_version", 127 | "application/json", 128 | bytes.NewReader(body), 129 | api.client.options.TokenOptions) 130 | if err != nil { 131 | return 132 | } 133 | defer res.Body.Close() 134 | task, err = getTask(getError(res)) 135 | return 136 | } 137 | -------------------------------------------------------------------------------- /photon/flavors_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = Describe("Flavor", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | flavorSpec *FlavorCreateSpec 23 | ) 24 | 25 | BeforeEach(func() { 26 | server, client = testSetup() 27 | flavorSpec = &FlavorCreateSpec{ 28 | Name: randomString(10, "go-sdk-flavor-"), 29 | Kind: "vm", 30 | Cost: []QuotaLineItem{QuotaLineItem{"GB", 16, "vm.memory"}}, 31 | } 32 | }) 33 | 34 | AfterEach(func() { 35 | cleanFlavors(client) 36 | server.Close() 37 | }) 38 | 39 | Describe("CreateGetAndDeleteFlavor", func() { 40 | It("Flavor create and delete succeeds", func() { 41 | mockTask := createMockTask("CREATE_FLAVOR", "COMPLETED") 42 | server.SetResponseJson(200, mockTask) 43 | 44 | flavorSpec := &FlavorCreateSpec{ 45 | Name: randomString(10, "go-sdk-flavor-"), 46 | Kind: "vm", 47 | Cost: []QuotaLineItem{QuotaLineItem{"GB", 16, "vm.memory"}}, 48 | } 49 | task, err := client.Flavors.Create(flavorSpec) 50 | 51 | GinkgoT().Log(err) 52 | Expect(err).Should(BeNil()) 53 | Expect(task).ShouldNot(BeNil()) 54 | Expect(task.Operation).Should(Equal("CREATE_FLAVOR")) 55 | Expect(task.State).Should(Equal("COMPLETED")) 56 | 57 | mockTask = createMockTask("DELETE_FLAVOR", "COMPLETED") 58 | server.SetResponseJson(200, mockTask) 59 | task, err = client.Flavors.Delete(task.Entity.ID) 60 | 61 | GinkgoT().Log(err) 62 | Expect(err).Should(BeNil()) 63 | Expect(task).ShouldNot(BeNil()) 64 | Expect(task.Operation).Should(Equal("DELETE_FLAVOR")) 65 | Expect(task.State).Should(Equal("COMPLETED")) 66 | }) 67 | }) 68 | 69 | Describe("GetFlavor", func() { 70 | var ( 71 | flavorName string 72 | flavorID string 73 | ) 74 | 75 | BeforeEach(func() { 76 | flavorName, flavorID = createFlavor(server, client) 77 | }) 78 | 79 | It("Get flavor succeeds", func() { 80 | server.SetResponseJson(200, Flavor{Name: flavorName}) 81 | flavor, err := client.Flavors.Get(flavorID) 82 | 83 | GinkgoT().Log(err) 84 | Expect(err).Should(BeNil()) 85 | Expect(flavor).ShouldNot(BeNil()) 86 | Expect(flavor.ID).Should(Equal(flavorID)) 87 | Expect(flavor.Name).Should(Equal(flavorName)) 88 | 89 | mockTask := createMockTask("DELETE_FLAVOR", "COMPLETED") 90 | server.SetResponseJson(200, mockTask) 91 | _, err = client.Flavors.Delete(flavorID) 92 | 93 | GinkgoT().Log(err) 94 | Expect(err).Should(BeNil()) 95 | }) 96 | 97 | It("Get all flavor succeeds", func() { 98 | mockFlavorsPage := createMockFlavorsPage(Flavor{Name: flavorName}) 99 | server.SetResponseJson(200, mockFlavorsPage) 100 | flavorList, err := client.Flavors.GetAll(&FlavorGetOptions{}) 101 | GinkgoT().Log(err) 102 | Expect(err).Should(BeNil()) 103 | Expect(flavorList).ShouldNot(BeNil()) 104 | 105 | var found bool 106 | for _, flavor := range flavorList.Items { 107 | if flavor.Name == flavorName && flavor.ID == flavorID { 108 | found = true 109 | break 110 | } 111 | } 112 | Expect(found).Should(BeTrue()) 113 | 114 | mockTask := createMockTask("DELETE_FLAVOR", "COMPLETED") 115 | server.SetResponseJson(200, mockTask) 116 | _, err = client.Flavors.Delete(flavorID) 117 | 118 | GinkgoT().Log(err) 119 | Expect(err).Should(BeNil()) 120 | }) 121 | }) 122 | 123 | Describe("GetTasks", func() { 124 | It("GetTasks returns a completed task", func() { 125 | mockTask := createMockTask("CREATE_FLAVOR", "COMPLETED") 126 | mockTask.Entity.ID = "mock-task-id" 127 | server.SetResponseJson(200, mockTask) 128 | 129 | task, err := client.Flavors.Create(flavorSpec) 130 | 131 | GinkgoT().Log(err) 132 | Expect(err).Should(BeNil()) 133 | 134 | mockTasksPage := createMockTasksPage(*mockTask) 135 | server.SetResponseJson(200, mockTasksPage) 136 | taskList, err := client.Flavors.GetTasks(task.Entity.ID, &TaskGetOptions{}) 137 | 138 | GinkgoT().Log(err) 139 | Expect(err).Should(BeNil()) 140 | Expect(taskList).ShouldNot(BeNil()) 141 | Expect(taskList.Items).Should(ContainElement(*task)) 142 | 143 | mockTask = createMockTask("DELETE_FLAVOR", "COMPLETED") 144 | server.SetResponseJson(200, mockTask) 145 | _, err = client.Flavors.Delete(task.Entity.ID) 146 | 147 | GinkgoT().Log(err) 148 | Expect(err).Should(BeNil()) 149 | }) 150 | }) 151 | }) 152 | -------------------------------------------------------------------------------- /photon/infrastructure_hosts.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for infrastructure hosts API. 18 | type InfraHostsAPI struct { 19 | client *Client 20 | } 21 | 22 | var InfraHostsUrl string = rootUrl + "/infrastructure/hosts" 23 | 24 | // Register a host with photon platform 25 | func (api *InfraHostsAPI) Create(hostSpec *HostCreateSpec) (task *Task, err error) { 26 | body, err := json.Marshal(hostSpec) 27 | if err != nil { 28 | return 29 | } 30 | res, err := api.client.restClient.Post( 31 | api.client.Endpoint+InfraHostsUrl, 32 | "application/json", 33 | bytes.NewReader(body), 34 | api.client.options.TokenOptions) 35 | if err != nil { 36 | return 37 | } 38 | defer res.Body.Close() 39 | task, err = getTask(getError(res)) 40 | return 41 | } 42 | 43 | // Gets all hosts. 44 | func (api *InfraHostsAPI) GetHosts() (result *Hosts, err error) { 45 | uri := api.client.Endpoint + InfraHostsUrl 46 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 47 | if err != nil { 48 | return 49 | } 50 | 51 | result = &Hosts{} 52 | err = json.Unmarshal(res, result) 53 | return 54 | } 55 | 56 | // Gets a host with the specified ID. 57 | func (api *InfraHostsAPI) Get(id string) (host *Host, err error) { 58 | res, err := api.client.restClient.Get(api.client.Endpoint+InfraHostsUrl+"/"+id, api.client.options.TokenOptions) 59 | if err != nil { 60 | return 61 | } 62 | defer res.Body.Close() 63 | res, err = getError(res) 64 | if err != nil { 65 | return 66 | } 67 | var result Host 68 | err = json.NewDecoder(res.Body).Decode(&result) 69 | return &result, err 70 | } 71 | 72 | // Deletes a host with specified ID. 73 | func (api *InfraHostsAPI) Delete(id string) (task *Task, err error) { 74 | res, err := api.client.restClient.Delete(api.client.Endpoint+InfraHostsUrl+"/"+id, api.client.options.TokenOptions) 75 | if err != nil { 76 | return 77 | } 78 | defer res.Body.Close() 79 | task, err = getTask(getError(res)) 80 | return 81 | } 82 | 83 | // Suspend the host with the specified id 84 | func (api *InfraHostsAPI) Suspend(id string) (task *Task, err error) { 85 | body := []byte{} 86 | res, err := api.client.restClient.Post( 87 | api.client.Endpoint+InfraHostsUrl+"/"+id+"/suspend", 88 | "application/json", 89 | bytes.NewReader(body), 90 | api.client.options.TokenOptions) 91 | if err != nil { 92 | return 93 | } 94 | defer res.Body.Close() 95 | task, err = getTask(getError(res)) 96 | return 97 | } 98 | 99 | // Gets all the VMs with the specified host ID. 100 | func (api *InfraHostsAPI) GetVMs(id string) (result *VMs, err error) { 101 | res, err := api.client.restClient.Get(api.client.Endpoint+InfraHostsUrl+"/"+id+"/vms", api.client.options.TokenOptions) 102 | if err != nil { 103 | return 104 | } 105 | defer res.Body.Close() 106 | res, err = getError(res) 107 | if err != nil { 108 | return 109 | } 110 | result = &VMs{} 111 | err = json.NewDecoder(res.Body).Decode(result) 112 | return 113 | } 114 | 115 | // Resume the host with the specified id 116 | func (api *InfraHostsAPI) Resume(id string) (task *Task, err error) { 117 | body := []byte{} 118 | res, err := api.client.restClient.Post( 119 | api.client.Endpoint+InfraHostsUrl+"/"+id+"/resume", 120 | "application/json", 121 | bytes.NewReader(body), 122 | api.client.options.TokenOptions) 123 | if err != nil { 124 | return 125 | } 126 | defer res.Body.Close() 127 | task, err = getTask(getError(res)) 128 | return 129 | } 130 | 131 | // Host with the specified id enter maintenance mode 132 | func (api *InfraHostsAPI) EnterMaintenanceMode(id string) (task *Task, err error) { 133 | body := []byte{} 134 | res, err := api.client.restClient.Post( 135 | api.client.Endpoint+InfraHostsUrl+"/"+id+"/enter-maintenance", 136 | "application/json", 137 | bytes.NewReader(body), 138 | api.client.options.TokenOptions) 139 | if err != nil { 140 | return 141 | } 142 | defer res.Body.Close() 143 | task, err = getTask(getError(res)) 144 | return 145 | } 146 | 147 | // Host with the specified id exit maintenance mode 148 | func (api *InfraHostsAPI) ExitMaintenanceMode(id string) (task *Task, err error) { 149 | body := []byte{} 150 | res, err := api.client.restClient.Post( 151 | api.client.Endpoint+InfraHostsUrl+"/"+id+"/exit-maintenance", 152 | "application/json", 153 | bytes.NewReader(body), 154 | api.client.options.TokenOptions) 155 | if err != nil { 156 | return 157 | } 158 | defer res.Body.Close() 159 | task, err = getTask(getError(res)) 160 | return 161 | } 162 | -------------------------------------------------------------------------------- /photon/lightwave/oidcclient_sspi.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | // +build windows 11 | 12 | package lightwave 13 | 14 | import ( 15 | "encoding/base64" 16 | "fmt" 17 | "github.com/vmware/photon-controller-go-sdk/SSPI" 18 | "math/rand" 19 | "net" 20 | "net/url" 21 | "strings" 22 | "time" 23 | ) 24 | 25 | const gssTicketGrantFormatString = "grant_type=urn:vmware:grant_type:gss_ticket&gss_ticket=%s&context_id=%s&scope=%s" 26 | 27 | // GetTokensFromWindowsLogInContext gets tokens based on Windows logged in context 28 | // Here is how it works: 29 | // 1. Get the SPN (Service Principal Name) in the format host/FQDN of lightwave. This is needed for SSPI/Kerberos protocol 30 | // 2. Call Windows API AcquireCredentialsHandle() using SSPI library. This will give the current users credential handle 31 | // 3. Using this handle call Windows API AcquireCredentialsHandle(). This will give you byte[] 32 | // 4. Encode this byte[] and send it to OIDC server over HTTP (using POST) 33 | // 5. OIDC server can send either of the following 34 | // - Access tokens. In this case return access tokens to client 35 | // - Error in the format: invalid_grant: gss_continue_needed:'context id':'token from server' 36 | // 6. In case you get error, parse it and get the token from server 37 | // 7. Feed this token to step 3 and repeat steps till you get the access tokens from server 38 | func (client *OIDCClient) GetTokensFromWindowsLogInContext() (tokens *OIDCTokenResponse, err error) { 39 | spn, err := client.buildSPN() 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | auth, _ := SSPI.GetAuth("", "", spn, "") 45 | 46 | userContext, err := auth.InitialBytes() 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | // In case of multiple req/res between client and server (as explained in above comment), 52 | // server needs to maintain the mapping of context id -> token 53 | // So we need to generate random string as a context id 54 | // If we use same context id for all the requests, results can be erroneous 55 | contextId := client.generateRandomString() 56 | body := fmt.Sprintf(gssTicketGrantFormatString, url.QueryEscape(base64.StdEncoding.EncodeToString(userContext)), contextId, client.Options.TokenScope) 57 | tokens, err = client.getToken(body) 58 | 59 | for { 60 | if err == nil { 61 | break 62 | } 63 | 64 | // In case of error the response will be in format: invalid_grant: gss_continue_needed:'context id':'token from server' 65 | gssToken := client.validateAndExtractGSSResponse(err, contextId) 66 | if gssToken == "" { 67 | return nil, err 68 | } 69 | 70 | data, err := base64.StdEncoding.DecodeString(gssToken) 71 | if err != nil { 72 | return nil, err 73 | } 74 | 75 | userContext, err := auth.NextBytes(data) 76 | body := fmt.Sprintf(gssTicketGrantFormatString, url.QueryEscape(base64.StdEncoding.EncodeToString(userContext)), contextId, client.Options.TokenScope) 77 | tokens, err = client.getToken(body) 78 | } 79 | 80 | return tokens, err 81 | } 82 | 83 | // Gets the SPN (Service Principal Name) in the format host/FQDN of lightwave 84 | func (client *OIDCClient) buildSPN() (spn string, err error) { 85 | u, err := url.Parse(client.Endpoint) 86 | if err != nil { 87 | return "", err 88 | } 89 | 90 | host, _, err := net.SplitHostPort(u.Host) 91 | if err != nil { 92 | return "", err 93 | } 94 | 95 | addr, err := net.LookupAddr(host) 96 | if err != nil { 97 | return "", err 98 | } 99 | 100 | var s = strings.TrimSuffix(addr[0], ".") 101 | return "host/" + s, nil 102 | } 103 | 104 | // validateAndExtractGSSResponse parse the error from server and returns token from server 105 | // In case of error from the server, response will be in format: invalid_grant: gss_continue_needed:'context id':'token from server' 106 | // So, we check for the above format in error and then return the token from server 107 | // If error is not in above format, we return empty string 108 | func (client *OIDCClient) validateAndExtractGSSResponse(err error, contextId string) string { 109 | parts := strings.Split(err.Error(), ":") 110 | if !(len(parts) == 4 && strings.TrimSpace(parts[1]) == "gss_continue_needed" && parts[2] == contextId) { 111 | return "" 112 | } else { 113 | return parts[3] 114 | } 115 | } 116 | 117 | func (client *OIDCClient) generateRandomString() string { 118 | const length = 10 119 | const asciiA = 65 120 | const asciiZ = 90 121 | rand.Seed(time.Now().UTC().UnixNano()) 122 | bytes := make([]byte, length) 123 | for i := 0; i < length; i++ { 124 | bytes[i] = byte(randInt(asciiA, asciiZ)) 125 | } 126 | return string(bytes) 127 | } 128 | 129 | func randInt(min int, max int) int { 130 | return min + rand.Intn(max-min) 131 | } 132 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Photon-controller-go-SDK 2 | 3 | # WARNING: Photon Controller GO SDK is no longer actively maintained by VMware. 4 | 5 | VMware has made the difficult decision to stop driving this project and therefore we will no longer actively respond 6 | to issues or pull requests. If you would like to take over maintaining this project independently from VMware, please 7 | let us know so we can add a link to your forked project here. 8 | 9 | Thank You. 10 | 11 | # Getting Started with Photon Controller SDK 12 | 13 | 1. If you haven't already, set up a Go workspace according to the 14 | [Go docs](http://golang.org/doc). 15 | 2. Install the Go SDK. Normally this is done with "go get". 16 | 3. Setup GOPATH environment variable 17 | 18 | Then: 19 | ``` 20 | mkdir -p $GOPATH/src/github.com/vmware 21 | cd $GOPATH/src/github.com/vmware 22 | git clone (github.com/vmware or gerrit)/photon-controller-go-sdk 23 | ``` 24 | 25 | # Testing for developers 26 | 27 | For our Go SDK, we use Ginkgo and Gomega testing framework. 28 | Install Gingko and Gomega: 29 | ``` 30 | go get github.com/onsi/ginkgo/ginkgo 31 | go get github.com/onsi/gomega 32 | ``` 33 | 34 | 35 | To run the tests, use the command: 36 | ``` 37 | go test ./... 38 | ``` 39 | 40 | You can add flags from both go and ginkgo for more detailed debugging. 41 | Some helpful flags: 42 | ``` 43 | -v Verbose output 44 | --ginkgo.v Verbose Ginkgo output (Prints all specs as they begin) 45 | --ginkgo.noColor No color output 46 | --ginkgo.slowSpecThreshold= Ignore Ginkgo warnings of slow tests under threshold seconds 47 | --ginkgo.focus Run specs that match the regex 48 | --ginkgo.skip Skip specs that match the regex 49 | ``` 50 | 51 | To run the SDK tests against a real deployment, you can set the following environment variables: 52 | ``` 53 | TEST_ENDPOINT Photon Controller Endpoint 54 | REAL_AGENT (optional) 55 | 56 | Photon Controller credentials in the form of either: 57 | API_ACCESS_TOKEN 58 | or 59 | USERNAME 60 | PASSWORD 61 | ``` 62 | 63 | Note: Some tests are skipped when run against a real deployment. 64 | 65 | 66 | **WARNING: Currently some tests are known to fail when run against a real deployment.** 67 | 68 | To run the Lightwave portion of SDK tests against a real Lightwave set the following environment variables: 69 | ``` 70 | LIGHTWAVE_ENDPOINT 71 | LIGHTWAVE_USERNAME 72 | LIGHTWAVE_PASSWORD 73 | ``` 74 | 75 | ## Sample App 76 | 77 | Here's a quick sample app that will retrieve Photon Controller status from a 78 | [local devbox]. 79 | In this example, it's under $GOPATH/src/sdkexample/main.go: 80 | 81 | ```golang 82 | package main 83 | 84 | import ( 85 | "fmt" 86 | "github.com/vmware/photon-controller-go-sdk/photon" 87 | "log" 88 | ) 89 | 90 | func main() { 91 | clientOptions := photon.ClientOptions{IgnoreCertificate: true} 92 | client := photon.NewClient("https://localhost:9000", &clientOptions, nil) 93 | tokenOptions, err := client.Auth.GetTokensByPassword("username", "password") 94 | if err != nil { 95 | log.Fatal(err) 96 | } 97 | clientOptions = photon.ClientOptions{IgnoreCertificate: true, TokenOptions: tokenOptions} 98 | client = photon.NewClient("https://localhost:9000", &clientOptions, nil) 99 | status, err := client.System.GetSystemStatus() 100 | if err != nil { 101 | log.Fatal(err) 102 | } 103 | fmt.Print(status) 104 | } 105 | ``` 106 | 107 | Then build it and run it: 108 | 109 | ``` 110 | cd $GOPATH/src/sdkexample 111 | go build ./... 112 | ./sdkexample 113 | ``` 114 | 115 | And the output should look something like this: 116 | `&{READY [{PHOTON_CONTROLLER READY}]}` 117 | 118 | ## Using APIs that return tasks 119 | 120 | Most Photon Controller APIs use a task model. The API will return a task object, 121 | which will indicate the state of the task (such as queued, completed, error, etc). 122 | These tasks return immediately and the caller must poll to find out when the task 123 | has been completed.The Go SDK provides a tasks API to do this for you, 124 | with built-in retry and error handling. 125 | 126 | Let's expand the sample app to create a new tenant: 127 | 128 | ``` 129 | package main 130 | 131 | import ( 132 | "fmt" 133 | "github.com/vmware/photon-controller-go-sdk/photon" 134 | "log" 135 | ) 136 | 137 | func main() { 138 | clientOptions := photon.ClientOptions{IgnoreCertificate: true} 139 | client := photon.NewClient("https://localhost:9000", &clientOptions, nil) 140 | tokenOptions, err := client.Auth.GetTokensByPassword("username", "password") 141 | if err != nil { 142 | log.Fatal(err) 143 | } 144 | clientOptions = photon.ClientOptions{IgnoreCertificate: true, TokenOptions: tokenOptions} 145 | client = photon.NewClient("https://localhost:9000", &clientOptions, nil) 146 | status, err := client.System.GetSystemStatus() 147 | if err != nil { 148 | log.Fatal(err) 149 | } 150 | fmt.Println(status) 151 | 152 | // Let's create a new tenant 153 | tenantSpec := &photon.TenantCreateSpec{Name: "new-tenant"} 154 | 155 | task, err := client.Tenants.Create(tenantSpec) 156 | if err != nil { 157 | log.Fatal(err) 158 | } 159 | 160 | // Wait for task completion 161 | task, err = client.Tasks.Wait(task.ID) 162 | if err != nil { 163 | log.Fatal(err) 164 | } 165 | fmt.Printf("ID of new tenant is: %s\n", task.Entity.ID) 166 | } 167 | 168 | ``` 169 | 170 | It should now output this: 171 | 172 | ``` 173 | &{READY [{PHOTON_CONTROLLER READY}]} 174 | ID of new tenant is: c8989a40-0fa4-4d9a-8e73-2fe4d28d0065 175 | ``` 176 | -------------------------------------------------------------------------------- /photon/routers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = Describe("Router", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | routerCreateSpec *RouterCreateSpec 23 | tenantID string 24 | projID string 25 | ) 26 | 27 | BeforeEach(func() { 28 | server, client = testSetup() 29 | tenantID = createTenant(server, client) 30 | projID = createProject(server, client, tenantID) 31 | routerCreateSpec = &RouterCreateSpec{Name: "router-1", PrivateIpCidr: "cidr1"} 32 | }) 33 | 34 | AfterEach(func() { 35 | cleanTenants(client) 36 | server.Close() 37 | }) 38 | 39 | Describe("CreateDeleteRouter", func() { 40 | It("Router create and delete succeeds", func() { 41 | mockTask := createMockTask("CREATE_ROUTER", "COMPLETED") 42 | server.SetResponseJson(200, mockTask) 43 | 44 | task, err := client.Projects.CreateRouter(projID, routerCreateSpec) 45 | task, err = client.Tasks.Wait(task.ID) 46 | GinkgoT().Log(err) 47 | Expect(err).Should(BeNil()) 48 | Expect(task).ShouldNot(BeNil()) 49 | Expect(task.Operation).Should(Equal("CREATE_ROUTER")) 50 | Expect(task.State).Should(Equal("COMPLETED")) 51 | 52 | mockTask = createMockTask("DELETE_ROUTER", "COMPLETED") 53 | server.SetResponseJson(200, mockTask) 54 | task, err = client.Routers.Delete("routerId") 55 | task, err = client.Tasks.Wait(task.ID) 56 | GinkgoT().Log(err) 57 | Expect(err).Should(BeNil()) 58 | Expect(task).ShouldNot(BeNil()) 59 | Expect(task.Operation).Should(Equal("DELETE_ROUTER")) 60 | Expect(task.State).Should(Equal("COMPLETED")) 61 | }) 62 | }) 63 | 64 | Describe("GetRouter", func() { 65 | It("Get returns router", func() { 66 | mockTask := createMockTask("CREATE_ROUTER", "COMPLETED") 67 | server.SetResponseJson(200, mockTask) 68 | 69 | task, err := client.Projects.CreateRouter(projID, routerCreateSpec) 70 | task, err = client.Tasks.Wait(task.ID) 71 | GinkgoT().Log(err) 72 | Expect(err).Should(BeNil()) 73 | Expect(task).ShouldNot(BeNil()) 74 | Expect(task.Operation).Should(Equal("CREATE_ROUTER")) 75 | Expect(task.State).Should(Equal("COMPLETED")) 76 | 77 | server.SetResponseJson(200, Router{Name: "router-1", PrivateIpCidr: "cidr1"}) 78 | router, err := client.Routers.Get(task.Entity.ID) 79 | 80 | GinkgoT().Log(err) 81 | Expect(err).Should(BeNil()) 82 | Expect(router).ShouldNot(BeNil()) 83 | 84 | var found bool 85 | if router.Name == "router-1" && router.ID == task.Entity.ID { 86 | found = true 87 | } 88 | Expect(found).Should(BeTrue()) 89 | 90 | mockTask = createMockTask("DELETE_ROUTER", "COMPLETED") 91 | server.SetResponseJson(200, mockTask) 92 | _, err = client.Routers.Delete(task.Entity.ID) 93 | 94 | GinkgoT().Log(err) 95 | Expect(err).Should(BeNil()) 96 | }) 97 | }) 98 | 99 | Describe("UpdateRouter", func() { 100 | It("update router's name", func() { 101 | mockTask := createMockTask("UPDATE_ROUTER", "COMPLETED") 102 | server.SetResponseJson(200, mockTask) 103 | 104 | routerSpec := &RouterUpdateSpec{RouterName: "router-1"} 105 | task, err := client.Routers.UpdateRouter("router-Id", routerSpec) 106 | task, err = client.Tasks.Wait(task.ID) 107 | GinkgoT().Log(err) 108 | Expect(err).Should(BeNil()) 109 | Expect(task).ShouldNot(BeNil()) 110 | Expect(task.Operation).Should(Equal("UPDATE_ROUTER")) 111 | Expect(task.State).Should(Equal("COMPLETED")) 112 | }) 113 | }) 114 | 115 | Describe("GetRouterSubnets", func() { 116 | It("GetAll returns subnet", func() { 117 | mockTask := createMockTask("CREATE_SUBNET", "COMPLETED") 118 | server.SetResponseJson(200, mockTask) 119 | subnetSpec := &SubnetCreateSpec{ 120 | Name: "subnet_name", 121 | Description: "subnet description", 122 | PrivateIpCidr: "192.168.0.1/24", 123 | } 124 | 125 | task, err := client.Routers.CreateSubnet("router-id", subnetSpec) 126 | task, err = client.Tasks.Wait(task.ID) 127 | GinkgoT().Log(err) 128 | Expect(err).Should(BeNil()) 129 | 130 | subnetMock := Subnet{ 131 | Name: subnetSpec.Name, 132 | Description: subnetSpec.Description, 133 | PrivateIpCidr: subnetSpec.PrivateIpCidr, 134 | } 135 | server.SetResponseJson(200, &Subnets{[]Subnet{subnetMock}}) 136 | subnetList, err := client.Routers.GetSubnets("router-id", &SubnetGetOptions{}) 137 | GinkgoT().Log(err) 138 | Expect(err).Should(BeNil()) 139 | Expect(subnetList).ShouldNot(BeNil()) 140 | 141 | var found bool 142 | for _, subnet := range subnetList.Items { 143 | if subnet.Name == subnetSpec.Name && subnet.ID == task.Entity.ID { 144 | found = true 145 | break 146 | } 147 | } 148 | Expect(found).Should(BeTrue()) 149 | 150 | mockTask = createMockTask("DELETE_SUBNET", "COMPLETED") 151 | server.SetResponseJson(200, mockTask) 152 | task, err = client.Subnets.Delete(task.Entity.ID) 153 | task, err = client.Tasks.Wait(task.ID) 154 | GinkgoT().Log(err) 155 | Expect(err).Should(BeNil()) 156 | }) 157 | }) 158 | 159 | }) 160 | -------------------------------------------------------------------------------- /photon/networks_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = Describe("Network", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | networkCreateSpec *NetworkCreateSpec 23 | tenantID string 24 | projID string 25 | ) 26 | 27 | BeforeEach(func() { 28 | server, client = testSetup() 29 | tenantID = createTenant(server, client) 30 | projID = createProject(server, client, tenantID) 31 | networkCreateSpec = &NetworkCreateSpec{Name: "network-1", PrivateIpCidr: "cidr1"} 32 | }) 33 | 34 | AfterEach(func() { 35 | cleanTenants(client) 36 | server.Close() 37 | }) 38 | 39 | Describe("CreateDeleteNetwork", func() { 40 | It("Network create and delete succeeds", func() { 41 | mockTask := createMockTask("CREATE_NETWORK", "COMPLETED") 42 | server.SetResponseJson(200, mockTask) 43 | 44 | task, err := client.Projects.CreateNetwork(projID, networkCreateSpec) 45 | task, err = client.Tasks.Wait(task.ID) 46 | GinkgoT().Log(err) 47 | Expect(err).Should(BeNil()) 48 | Expect(task).ShouldNot(BeNil()) 49 | Expect(task.Operation).Should(Equal("CREATE_NETWORK")) 50 | Expect(task.State).Should(Equal("COMPLETED")) 51 | 52 | mockTask = createMockTask("DELETE_NETWORK", "COMPLETED") 53 | server.SetResponseJson(200, mockTask) 54 | task, err = client.Networks.Delete("networkId") 55 | task, err = client.Tasks.Wait(task.ID) 56 | GinkgoT().Log(err) 57 | Expect(err).Should(BeNil()) 58 | Expect(task).ShouldNot(BeNil()) 59 | Expect(task.Operation).Should(Equal("DELETE_NETWORK")) 60 | Expect(task.State).Should(Equal("COMPLETED")) 61 | }) 62 | }) 63 | 64 | Describe("GetNetwork", func() { 65 | It("Get returns network", func() { 66 | mockTask := createMockTask("CREATE_NETWORK", "COMPLETED") 67 | server.SetResponseJson(200, mockTask) 68 | 69 | task, err := client.Projects.CreateNetwork(projID, networkCreateSpec) 70 | task, err = client.Tasks.Wait(task.ID) 71 | GinkgoT().Log(err) 72 | Expect(err).Should(BeNil()) 73 | Expect(task).ShouldNot(BeNil()) 74 | Expect(task.Operation).Should(Equal("CREATE_NETWORK")) 75 | Expect(task.State).Should(Equal("COMPLETED")) 76 | 77 | server.SetResponseJson(200, Network{Name: "network-1", PrivateIpCidr: "cidr1"}) 78 | network, err := client.Networks.Get(task.Entity.ID) 79 | 80 | GinkgoT().Log(err) 81 | Expect(err).Should(BeNil()) 82 | Expect(network).ShouldNot(BeNil()) 83 | 84 | var found bool 85 | if network.Name == "network-1" && network.ID == task.Entity.ID { 86 | found = true 87 | } 88 | Expect(found).Should(BeTrue()) 89 | 90 | mockTask = createMockTask("DELETE_NETWORK", "COMPLETED") 91 | server.SetResponseJson(200, mockTask) 92 | _, err = client.Networks.Delete(task.Entity.ID) 93 | 94 | GinkgoT().Log(err) 95 | Expect(err).Should(BeNil()) 96 | }) 97 | }) 98 | 99 | Describe("UpdateNetwork", func() { 100 | It("update network's name", func() { 101 | mockTask := createMockTask("UPDATE_NETWORK", "COMPLETED") 102 | server.SetResponseJson(200, mockTask) 103 | 104 | networkSpec := &NetworkUpdateSpec{NetworkName: "network-1"} 105 | task, err := client.Networks.UpdateNetwork("network-Id", networkSpec) 106 | task, err = client.Tasks.Wait(task.ID) 107 | GinkgoT().Log(err) 108 | Expect(err).Should(BeNil()) 109 | Expect(task).ShouldNot(BeNil()) 110 | Expect(task.Operation).Should(Equal("UPDATE_NETWORK")) 111 | Expect(task.State).Should(Equal("COMPLETED")) 112 | }) 113 | }) 114 | 115 | Describe("GetNetworkSubnets", func() { 116 | It("GetAll returns subnet", func() { 117 | mockTask := createMockTask("CREATE_SUBNET", "COMPLETED") 118 | server.SetResponseJson(200, mockTask) 119 | subnetSpec := &SubnetCreateSpec{ 120 | Name: "subnet_name", 121 | Description: "subnet description", 122 | PrivateIpCidr: "192.168.0.1/24", 123 | } 124 | 125 | task, err := client.Networks.CreateSubnet("network-id", subnetSpec) 126 | task, err = client.Tasks.Wait(task.ID) 127 | GinkgoT().Log(err) 128 | Expect(err).Should(BeNil()) 129 | 130 | subnetMock := Subnet{ 131 | Name: subnetSpec.Name, 132 | Description: subnetSpec.Description, 133 | PrivateIpCidr: subnetSpec.PrivateIpCidr, 134 | } 135 | server.SetResponseJson(200, &Subnets{[]Subnet{subnetMock}}) 136 | subnetList, err := client.Networks.GetSubnets("network-id", &SubnetGetOptions{}) 137 | GinkgoT().Log(err) 138 | Expect(err).Should(BeNil()) 139 | Expect(subnetList).ShouldNot(BeNil()) 140 | 141 | var found bool 142 | for _, subnet := range subnetList.Items { 143 | if subnet.Name == subnetSpec.Name && subnet.ID == task.Entity.ID { 144 | found = true 145 | break 146 | } 147 | } 148 | Expect(found).Should(BeTrue()) 149 | 150 | mockTask = createMockTask("DELETE_SUBNET", "COMPLETED") 151 | server.SetResponseJson(200, mockTask) 152 | task, err = client.Subnets.Delete(task.Entity.ID) 153 | task, err = client.Tasks.Wait(task.ID) 154 | GinkgoT().Log(err) 155 | Expect(err).Should(BeNil()) 156 | }) 157 | }) 158 | 159 | }) 160 | -------------------------------------------------------------------------------- /photon/images.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | "io" 16 | ) 17 | 18 | // Contains functionality for images API. 19 | type ImagesAPI struct { 20 | client *Client 21 | } 22 | 23 | // Options for GetImage API. 24 | type ImageGetOptions struct { 25 | Name string `urlParam:"name"` 26 | } 27 | 28 | var imageUrl string = rootUrl + "/images" 29 | 30 | // Uploads a new image, reading from the specified image path. 31 | // If options is nil, default options are used. 32 | func (api *ImagesAPI) CreateFromFile(imagePath string, options *ImageCreateOptions) (task *Task, err error) { 33 | params := imageCreateOptionsToMap(options) 34 | res, err := api.client.restClient.MultipartUploadFile(api.client.Endpoint+imageUrl, imagePath, params, api.client.options.TokenOptions) 35 | if err != nil { 36 | return 37 | } 38 | defer res.Body.Close() 39 | result, err := getTask(getError(res)) 40 | return result, err 41 | } 42 | 43 | // Uploads a new image, reading from the specified io.Reader. 44 | // Name is a descriptive name of the image, it is used in the filename field of the Content-Disposition header, 45 | // and does not need to be unique. 46 | // If options is nil, default options are used. 47 | func (api *ImagesAPI) Create(reader io.ReadSeeker, name string, options *ImageCreateOptions) (task *Task, err error) { 48 | params := imageCreateOptionsToMap(options) 49 | res, err := api.client.restClient.MultipartUpload(api.client.Endpoint+imageUrl, reader, name, params, api.client.options.TokenOptions) 50 | if err != nil { 51 | return 52 | } 53 | defer res.Body.Close() 54 | result, err := getTask(getError(res)) 55 | return result, err 56 | } 57 | 58 | // Gets all images on this photon instance. 59 | func (api *ImagesAPI) GetAll(options *ImageGetOptions) (images *Images, err error) { 60 | uri := api.client.Endpoint + imageUrl 61 | if options != nil { 62 | uri += getQueryString(options) 63 | } 64 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 65 | if err != nil { 66 | return 67 | } 68 | 69 | images = &Images{} 70 | err = json.Unmarshal(res, images) 71 | return 72 | } 73 | 74 | // Gets details of image with the specified ID. 75 | func (api *ImagesAPI) Get(imageID string) (image *Image, err error) { 76 | res, err := api.client.restClient.Get(api.client.Endpoint+imageUrl+"/"+imageID, api.client.options.TokenOptions) 77 | if err != nil { 78 | return 79 | } 80 | defer res.Body.Close() 81 | res, err = getError(res) 82 | if err != nil { 83 | return 84 | } 85 | var result Image 86 | err = json.NewDecoder(res.Body).Decode(&result) 87 | return &result, err 88 | } 89 | 90 | // Deletes image with the specified ID. 91 | func (api *ImagesAPI) Delete(imageID string) (task *Task, err error) { 92 | res, err := api.client.restClient.Delete(api.client.Endpoint+imageUrl+"/"+imageID, api.client.options.TokenOptions) 93 | if err != nil { 94 | return 95 | } 96 | defer res.Body.Close() 97 | result, err := getTask(getError(res)) 98 | return result, err 99 | } 100 | 101 | // Gets all tasks with the specified image ID, using options to filter the results. 102 | // If options is nil, no filtering will occur. 103 | func (api *ImagesAPI) GetTasks(id string, options *TaskGetOptions) (result *TaskList, err error) { 104 | uri := api.client.Endpoint + imageUrl + "/" + id + "/tasks" 105 | if options != nil { 106 | uri += getQueryString(options) 107 | } 108 | 109 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 110 | if err != nil { 111 | return 112 | } 113 | 114 | result = &TaskList{} 115 | err = json.Unmarshal(res, result) 116 | return 117 | } 118 | 119 | // Gets IAM Policy of an image. 120 | func (api *ImagesAPI) GetIam(imageID string) (policy []*RoleBinding, err error) { 121 | res, err := api.client.restClient.Get( 122 | api.client.Endpoint+imageUrl+"/"+imageID+"/iam", 123 | api.client.options.TokenOptions) 124 | if err != nil { 125 | return 126 | } 127 | defer res.Body.Close() 128 | res, err = getError(res) 129 | if err != nil { 130 | return 131 | } 132 | err = json.NewDecoder(res.Body).Decode(&policy) 133 | return policy, err 134 | } 135 | 136 | // Sets IAM Policy on an image. 137 | func (api *ImagesAPI) SetIam(imageID string, policy []*RoleBinding) (task *Task, err error) { 138 | body, err := json.Marshal(policy) 139 | if err != nil { 140 | return 141 | } 142 | res, err := api.client.restClient.Post( 143 | api.client.Endpoint+imageUrl+"/"+imageID+"/iam", 144 | "application/json", 145 | bytes.NewReader(body), 146 | api.client.options.TokenOptions) 147 | if err != nil { 148 | return 149 | } 150 | defer res.Body.Close() 151 | task, err = getTask(getError(res)) 152 | return 153 | } 154 | 155 | // Modifies IAM Policy on an image. 156 | func (api *ImagesAPI) ModifyIam(imageID string, policyDelta []*RoleBindingDelta) (task *Task, err error) { 157 | body, err := json.Marshal(policyDelta) 158 | if err != nil { 159 | return 160 | } 161 | res, err := api.client.restClient.Patch( 162 | api.client.Endpoint+imageUrl+"/"+imageID+"/iam", 163 | "application/json", 164 | bytes.NewReader(body), 165 | api.client.options.TokenOptions) 166 | if err != nil { 167 | return 168 | } 169 | defer res.Body.Close() 170 | task, err = getTask(getError(res)) 171 | return 172 | } 173 | 174 | func imageCreateOptionsToMap(opts *ImageCreateOptions) map[string]string { 175 | if opts == nil { 176 | return nil 177 | } 178 | return map[string]string{ 179 | "ImageReplication": opts.ReplicationType, 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /photon/client.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "crypto/tls" 14 | "crypto/x509" 15 | "io/ioutil" 16 | "log" 17 | "net/http" 18 | "strings" 19 | "time" 20 | ) 21 | 22 | // Represents stateless context needed to call photon APIs. 23 | type Client struct { 24 | options ClientOptions 25 | restClient *restClient 26 | logger *log.Logger 27 | Endpoint string 28 | Tenants *TenantsAPI 29 | Tasks *TasksAPI 30 | Projects *ProjectsAPI 31 | Flavors *FlavorsAPI 32 | Images *ImagesAPI 33 | Disks *DisksAPI 34 | VMs *VmAPI 35 | Hosts *HostsAPI 36 | Datastores *DatastoresAPI 37 | Services *ServicesAPI 38 | Auth *AuthAPI 39 | Info *InfoAPI 40 | Routers *RoutersAPI 41 | Networks *NetworksAPI 42 | Subnets *SubnetsAPI 43 | System *SystemAPI 44 | Zones *ZonesAPI 45 | Infra *InfraAPI 46 | InfraHosts *InfraHostsAPI 47 | } 48 | 49 | // Represents Tokens 50 | type TokenOptions struct { 51 | AccessToken string `json:"access_token"` 52 | ExpiresIn int `json:"expires_in"` 53 | RefreshToken string `json:"refresh_token,omitempty"` 54 | IdToken string `json:"id_token"` 55 | TokenType string `json:"token_type"` 56 | } 57 | 58 | type TokenCallback func(string) 59 | 60 | // Options for Client 61 | type ClientOptions struct { 62 | // When using the Tasks.Wait APIs, defines the duration of how long 63 | // the SDK should continue to poll the server. Default is 30 minutes. 64 | // TasksAPI.WaitTimeout() can be used to specify timeout on 65 | // individual calls. 66 | TaskPollTimeout time.Duration 67 | 68 | // Whether or not to ignore any TLS errors when talking to photon, 69 | // false by default. 70 | IgnoreCertificate bool 71 | 72 | // List of root CA's to use for server validation 73 | // nil by default. 74 | RootCAs *x509.CertPool 75 | 76 | // For tasks APIs, defines the delay between each polling attempt. 77 | // Default is 100 milliseconds. 78 | TaskPollDelay time.Duration 79 | 80 | // For tasks APIs, defines the number of retries to make in the event 81 | // of an error. Default is 3. 82 | TaskRetryCount int 83 | 84 | // Tokens for user authentication. Default is empty. 85 | TokenOptions *TokenOptions 86 | 87 | // A function to be called if the access token was refreshed 88 | // The client can save the new access token for future API 89 | // calls so that it doesn't need to be refreshed again. 90 | UpdateAccessTokenCallback TokenCallback 91 | } 92 | 93 | // Creates a new photon client with specified options. If options 94 | // is nil, default options will be used. 95 | func NewClient(endpoint string, options *ClientOptions, logger *log.Logger) (c *Client) { 96 | defaultOptions := &ClientOptions{ 97 | TaskPollTimeout: 30 * time.Minute, 98 | TaskPollDelay: 100 * time.Millisecond, 99 | TaskRetryCount: 3, 100 | TokenOptions: &TokenOptions{}, 101 | IgnoreCertificate: false, 102 | RootCAs: nil, 103 | } 104 | 105 | if options != nil { 106 | if options.TaskPollTimeout != 0 { 107 | defaultOptions.TaskPollTimeout = options.TaskPollTimeout 108 | } 109 | if options.TaskPollDelay != 0 { 110 | defaultOptions.TaskPollDelay = options.TaskPollDelay 111 | } 112 | if options.TaskRetryCount != 0 { 113 | defaultOptions.TaskRetryCount = options.TaskRetryCount 114 | } 115 | if options.TokenOptions != nil { 116 | defaultOptions.TokenOptions = options.TokenOptions 117 | } 118 | if options.RootCAs != nil { 119 | defaultOptions.RootCAs = options.RootCAs 120 | } 121 | defaultOptions.IgnoreCertificate = options.IgnoreCertificate 122 | defaultOptions.UpdateAccessTokenCallback = options.UpdateAccessTokenCallback 123 | } 124 | 125 | if logger == nil { 126 | logger = createPassThroughLogger() 127 | } 128 | 129 | tr := &http.Transport{ 130 | TLSClientConfig: &tls.Config{ 131 | InsecureSkipVerify: defaultOptions.IgnoreCertificate, 132 | RootCAs: defaultOptions.RootCAs}, 133 | } 134 | 135 | endpoint = strings.TrimRight(endpoint, "/") 136 | 137 | tokenCallback := func(newToken string) { 138 | c.options.TokenOptions.AccessToken = newToken 139 | if c.options.UpdateAccessTokenCallback != nil { 140 | c.options.UpdateAccessTokenCallback(newToken) 141 | } 142 | } 143 | 144 | restClient := &restClient{ 145 | httpClient: &http.Client{Transport: tr}, 146 | logger: logger, 147 | UpdateAccessTokenCallback: tokenCallback, 148 | } 149 | 150 | c = &Client{Endpoint: endpoint, restClient: restClient, logger: logger} 151 | 152 | // Ensure a copy of options is made, rather than using a pointer 153 | // which may change out from underneath if misused by the caller. 154 | c.options = *defaultOptions 155 | c.Tenants = &TenantsAPI{c} 156 | c.Tasks = &TasksAPI{c} 157 | c.Projects = &ProjectsAPI{c} 158 | c.Flavors = &FlavorsAPI{c} 159 | c.Images = &ImagesAPI{c} 160 | c.Disks = &DisksAPI{c} 161 | c.VMs = &VmAPI{c} 162 | c.Hosts = &HostsAPI{c} 163 | c.Datastores = &DatastoresAPI{c} 164 | c.Services = &ServicesAPI{c} 165 | c.Auth = &AuthAPI{c} 166 | c.Info = &InfoAPI{c} 167 | c.Routers = &RoutersAPI{c} 168 | c.Networks = &NetworksAPI{c} 169 | c.Subnets = &SubnetsAPI{c} 170 | c.System = &SystemAPI{c} 171 | c.Zones = &ZonesAPI{c} 172 | c.Infra = &InfraAPI{c} 173 | c.InfraHosts = &InfraHostsAPI{c} 174 | 175 | // Tell the restClient about the Auth API so it can request new 176 | // acces tokens when they expire 177 | restClient.Auth = c.Auth 178 | return 179 | } 180 | 181 | // Creates a new photon client with specified options and http.Client. 182 | // Useful for functional testing where http calls must be mocked out. 183 | // If options is nil, default options will be used. 184 | func NewTestClient(endpoint string, options *ClientOptions, httpClient *http.Client) (c *Client) { 185 | c = NewClient(endpoint, options, nil) 186 | c.restClient.httpClient = httpClient 187 | return 188 | } 189 | 190 | func createPassThroughLogger() (l *log.Logger) { 191 | // ioutil.Discard makes all logging operation be a no-op. 192 | return log.New(ioutil.Discard, "", log.LstdFlags) 193 | } 194 | -------------------------------------------------------------------------------- /photon/system.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | ) 16 | 17 | // Contains functionality for system API. 18 | type SystemAPI struct { 19 | client *Client 20 | } 21 | 22 | var systemUrl string = rootUrl + "/system" 23 | 24 | // Get status of photon controller 25 | func (api *SystemAPI) GetSystemStatus() (status *Status, err error) { 26 | res, err := api.client.restClient.Get(api.getEndpointUrl("status"), api.client.options.TokenOptions) 27 | if err != nil { 28 | return 29 | } 30 | defer res.Body.Close() 31 | res, err = getError(res) 32 | if err != nil { 33 | return 34 | } 35 | status = &Status{} 36 | err = json.NewDecoder(res.Body).Decode(status) 37 | return 38 | } 39 | 40 | // Gets the system info. 41 | func (api *SystemAPI) GetSystemInfo() (systemInfo *SystemInfo, err error) { 42 | res, err := api.client.restClient.Get(api.getEndpointUrl("info"), api.client.options.TokenOptions) 43 | if err != nil { 44 | return 45 | } 46 | defer res.Body.Close() 47 | res, err = getError(res) 48 | if err != nil { 49 | return 50 | } 51 | var result SystemInfo 52 | err = json.NewDecoder(res.Body).Decode(&result) 53 | return &result, err 54 | } 55 | 56 | // Pause system. 57 | func (api *SystemAPI) PauseSystem() (task *Task, err error) { 58 | res, err := api.client.restClient.Post( 59 | api.getEndpointUrl("pause"), 60 | "application/json", 61 | bytes.NewReader([]byte("")), 62 | api.client.options.TokenOptions) 63 | if err != nil { 64 | return 65 | } 66 | defer res.Body.Close() 67 | 68 | task, err = getTask(getError(res)) 69 | return 70 | } 71 | 72 | // Pause system background tasks. 73 | func (api *SystemAPI) PauseBackgroundTasks() (task *Task, err error) { 74 | res, err := api.client.restClient.Post( 75 | api.getEndpointUrl("pause-background-tasks"), 76 | "application/json", 77 | bytes.NewReader([]byte("")), 78 | api.client.options.TokenOptions) 79 | if err != nil { 80 | return 81 | } 82 | defer res.Body.Close() 83 | 84 | task, err = getTask(getError(res)) 85 | return 86 | } 87 | 88 | // Resume system. 89 | func (api *SystemAPI) ResumeSystem() (task *Task, err error) { 90 | res, err := api.client.restClient.Post( 91 | api.getEndpointUrl("resume"), 92 | "application/json", 93 | bytes.NewReader([]byte("")), 94 | api.client.options.TokenOptions) 95 | if err != nil { 96 | return 97 | } 98 | defer res.Body.Close() 99 | 100 | task, err = getTask(getError(res)) 101 | return 102 | } 103 | 104 | // Sets security groups for the system 105 | func (api *SystemAPI) SetSecurityGroups(securityGroups *SecurityGroupsSpec) (task *Task, err error) { 106 | body, err := json.Marshal(securityGroups) 107 | if err != nil { 108 | return 109 | } 110 | url := api.getEndpointUrl("set-security-groups") 111 | res, err := api.client.restClient.Post( 112 | url, 113 | "application/json", 114 | bytes.NewReader(body), 115 | api.client.options.TokenOptions) 116 | if err != nil { 117 | return 118 | } 119 | defer res.Body.Close() 120 | task, err = getTask(getError(res)) 121 | return 122 | } 123 | 124 | // Gets the system info. 125 | func (api *SystemAPI) GetSystemSize() (deploymentSize *SystemUsage, err error) { 126 | res, err := api.client.restClient.Get(api.getEndpointUrl("usage"), api.client.options.TokenOptions) 127 | if err != nil { 128 | return 129 | } 130 | defer res.Body.Close() 131 | res, err = getError(res) 132 | if err != nil { 133 | return 134 | } 135 | var result SystemUsage 136 | err = json.NewDecoder(res.Body).Decode(&result) 137 | return &result, err 138 | } 139 | 140 | // Gets authentication info. 141 | func (api *SystemAPI) GetAuthInfo() (info *AuthInfo, err error) { 142 | res, err := api.client.restClient.Get(api.getEndpointUrl("auth"), nil) 143 | if err != nil { 144 | return 145 | } 146 | defer res.Body.Close() 147 | res, err = getError(res) 148 | if err != nil { 149 | return 150 | } 151 | info = &AuthInfo{} 152 | err = json.NewDecoder(res.Body).Decode(info) 153 | return 154 | } 155 | 156 | // Gets all the system vms 157 | func (api *SystemAPI) GetSystemVms() (result *VMs, err error) { 158 | res, err := api.client.restClient.GetList(api.client.Endpoint, api.getEndpointUrl("vms"), 159 | api.client.options.TokenOptions) 160 | if err != nil { 161 | return 162 | } 163 | 164 | result = &VMs{} 165 | err = json.Unmarshal(res, result) 166 | return 167 | } 168 | 169 | // Enable service type 170 | func (api *SystemAPI) EnableServiceType(serviceConfigSpec *ServiceConfigurationSpec) (task *Task, err error) { 171 | body, err := json.Marshal(serviceConfigSpec) 172 | if err != nil { 173 | return 174 | } 175 | res, err := api.client.restClient.Post( 176 | api.getEndpointUrl("enable-service-type"), 177 | "application/json", 178 | bytes.NewReader(body), 179 | api.client.options.TokenOptions) 180 | if err != nil { 181 | return 182 | } 183 | defer res.Body.Close() 184 | 185 | task, err = getTask(getError(res)) 186 | return 187 | } 188 | 189 | // Disable service type 190 | func (api *SystemAPI) DisableServiceType(serviceConfigSpec *ServiceConfigurationSpec) (task *Task, err error) { 191 | body, err := json.Marshal(serviceConfigSpec) 192 | if err != nil { 193 | return 194 | } 195 | res, err := api.client.restClient.Post( 196 | api.getEndpointUrl("disable-service-type"), 197 | "application/json", 198 | bytes.NewReader(body), 199 | api.client.options.TokenOptions) 200 | if err != nil { 201 | return 202 | } 203 | defer res.Body.Close() 204 | 205 | task, err = getTask(getError(res)) 206 | return 207 | } 208 | 209 | // Configure NSX. 210 | func (api *SystemAPI) ConfigureNsx(nsxConfigSpec *NsxConfigurationSpec) (task *Task, err error) { 211 | body, err := json.Marshal(nsxConfigSpec) 212 | if err != nil { 213 | return 214 | } 215 | 216 | res, err := api.client.restClient.Post( 217 | api.getEndpointUrl("configure-nsx"), 218 | "application/json", 219 | bytes.NewReader(body), 220 | api.client.options.TokenOptions) 221 | if err != nil { 222 | return 223 | } 224 | defer res.Body.Close() 225 | 226 | task, err = getTask(getError(res)) 227 | return 228 | } 229 | 230 | func (api *SystemAPI) getEndpointUrl(endpoint string) (url string) { 231 | return api.client.Endpoint + systemUrl + "/" + endpoint 232 | } 233 | -------------------------------------------------------------------------------- /photon/zones_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = Describe("Zone", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | ) 23 | 24 | BeforeEach(func() { 25 | server, client = testSetup() 26 | }) 27 | 28 | AfterEach(func() { 29 | server.Close() 30 | }) 31 | 32 | Describe("CreateAndDeleteZone", func() { 33 | It("Zone create and delete succeeds", func() { 34 | mockTask := createMockTask("CREATE_ZONE", "COMPLETED") 35 | server.SetResponseJson(200, mockTask) 36 | zoneSpec := &ZoneCreateSpec{Name: randomString(10, "go-sdk-zone-")} 37 | task, err := client.Zones.Create(zoneSpec) 38 | task, err = client.Tasks.Wait(task.ID) 39 | 40 | GinkgoT().Log(err) 41 | Expect(err).Should(BeNil()) 42 | Expect(task).ShouldNot(BeNil()) 43 | Expect(task.Operation).Should(Equal("CREATE_ZONE")) 44 | Expect(task.State).Should(Equal("COMPLETED")) 45 | 46 | mockTask = createMockTask("DELETE_ZONE", "COMPLETED") 47 | server.SetResponseJson(200, mockTask) 48 | task, err = client.Zones.Delete(task.Entity.ID) 49 | 50 | GinkgoT().Log(err) 51 | Expect(err).Should(BeNil()) 52 | Expect(task).ShouldNot(BeNil()) 53 | Expect(task.Operation).Should(Equal("DELETE_ZONE")) 54 | Expect(task.State).Should(Equal("COMPLETED")) 55 | }) 56 | 57 | It("Zone create fails", func() { 58 | zoneSpec := &ZoneCreateSpec{} 59 | task, err := client.Zones.Create(zoneSpec) 60 | 61 | Expect(err).ShouldNot(BeNil()) 62 | Expect(task).Should(BeNil()) 63 | }) 64 | }) 65 | 66 | Describe("GetZone", func() { 67 | It("Get returns zone", func() { 68 | mockTask := createMockTask("CREATE_ZONE", "COMPLETED") 69 | server.SetResponseJson(200, mockTask) 70 | zoneName := randomString(10, "go-sdk-zone-") 71 | zoneSpec := &ZoneCreateSpec{Name: zoneName} 72 | task, err := client.Zones.Create(zoneSpec) 73 | 74 | GinkgoT().Log(err) 75 | Expect(err).Should(BeNil()) 76 | Expect(task).ShouldNot(BeNil()) 77 | Expect(task.Operation).Should(Equal("CREATE_ZONE")) 78 | Expect(task.State).Should(Equal("COMPLETED")) 79 | 80 | server.SetResponseJson(200, Zone{Name: zoneName}) 81 | zone, err := client.Zones.Get(task.Entity.ID) 82 | 83 | GinkgoT().Log(err) 84 | Expect(err).Should(BeNil()) 85 | Expect(zone).ShouldNot(BeNil()) 86 | 87 | var found bool 88 | if zone.Name == zoneName && zone.ID == task.Entity.ID { 89 | found = true 90 | } 91 | Expect(found).Should(BeTrue()) 92 | 93 | mockTask = createMockTask("DELETE_ZONE", "COMPLETED") 94 | server.SetResponseJson(200, mockTask) 95 | _, err = client.Zones.Delete(task.Entity.ID) 96 | 97 | GinkgoT().Log(err) 98 | Expect(err).Should(BeNil()) 99 | }) 100 | 101 | It("Get all returns zones", func() { 102 | mockTask := createMockTask("CREATE_ZONE", "COMPLETED") 103 | server.SetResponseJson(200, mockTask) 104 | zoneName := randomString(10, "go-sdk-zone-") 105 | zoneSpec := &ZoneCreateSpec{Name: zoneName} 106 | task, err := client.Zones.Create(zoneSpec) 107 | 108 | GinkgoT().Log(err) 109 | Expect(err).Should(BeNil()) 110 | Expect(task).ShouldNot(BeNil()) 111 | Expect(task.Operation).Should(Equal("CREATE_ZONE")) 112 | Expect(task.State).Should(Equal("COMPLETED")) 113 | 114 | zonePage := createMockZonesPage(Zone{Name: zoneName}) 115 | server.SetResponseJson(200, zonePage) 116 | zones, err := client.Zones.GetAll() 117 | 118 | GinkgoT().Log(err) 119 | Expect(err).Should(BeNil()) 120 | Expect(zones).ShouldNot(BeNil()) 121 | 122 | var found bool 123 | for _, zone := range zones.Items { 124 | if zone.Name == zoneName && zone.ID == task.Entity.ID { 125 | found = true 126 | break 127 | } 128 | } 129 | Expect(found).Should(BeTrue()) 130 | 131 | mockTask = createMockTask("DELETE_ZONE", "COMPLETED") 132 | server.SetResponseJson(200, mockTask) 133 | _, err = client.Zones.Delete(task.Entity.ID) 134 | 135 | GinkgoT().Log(err) 136 | Expect(err).Should(BeNil()) 137 | }) 138 | }) 139 | 140 | Describe("GetZoneTasks", func() { 141 | var ( 142 | option string 143 | ) 144 | 145 | Context("no extra options for GetTask", func() { 146 | BeforeEach(func() { 147 | option = "" 148 | }) 149 | 150 | It("GetTasks returns a completed task", func() { 151 | mockTask := createMockTask("CREATE_ZONE", "COMPLETED") 152 | mockTask.Entity.ID = "mock-task-id" 153 | server.SetResponseJson(200, mockTask) 154 | zoneSpec := &ZoneCreateSpec{Name: randomString(10, "go-sdk-zone-")} 155 | task, err := client.Zones.Create(zoneSpec) 156 | 157 | GinkgoT().Log(err) 158 | Expect(err).Should(BeNil()) 159 | Expect(task).ShouldNot(BeNil()) 160 | Expect(task.Operation).Should(Equal("CREATE_ZONE")) 161 | Expect(task.State).Should(Equal("COMPLETED")) 162 | 163 | mockTasksPage := createMockTasksPage(*mockTask) 164 | server.SetResponseJson(200, mockTasksPage) 165 | taskList, err := client.Zones.GetTasks(task.Entity.ID, &TaskGetOptions{State: option}) 166 | GinkgoT().Log(err) 167 | Expect(err).Should(BeNil()) 168 | Expect(taskList).ShouldNot(BeNil()) 169 | Expect(taskList.Items).Should(ContainElement(*task)) 170 | 171 | mockTask = createMockTask("DELETE_ZONE", "COMPLETED") 172 | server.SetResponseJson(200, mockTask) 173 | _, err = client.Zones.Delete(task.Entity.ID) 174 | 175 | GinkgoT().Log(err) 176 | Expect(err).Should(BeNil()) 177 | }) 178 | }) 179 | 180 | Context("Searching COMPLETED state for GetTask", func() { 181 | BeforeEach(func() { 182 | option = "COMPLETED" 183 | }) 184 | 185 | It("GetTasks returns a completed task", func() { 186 | mockTask := createMockTask("CREATE_ZONE", "COMPLETED") 187 | mockTask.Entity.ID = "mock-task-id" 188 | server.SetResponseJson(200, mockTask) 189 | zoneSpec := &ZoneCreateSpec{Name: randomString(10, "go-sdk-zone-")} 190 | task, err := client.Zones.Create(zoneSpec) 191 | 192 | GinkgoT().Log(err) 193 | Expect(err).Should(BeNil()) 194 | Expect(task).ShouldNot(BeNil()) 195 | Expect(task.Operation).Should(Equal("CREATE_ZONE")) 196 | Expect(task.State).Should(Equal("COMPLETED")) 197 | 198 | mockTasksPage := createMockTasksPage(*mockTask) 199 | server.SetResponseJson(200, mockTasksPage) 200 | taskList, err := client.Zones.GetTasks(task.Entity.ID, &TaskGetOptions{State: option}) 201 | GinkgoT().Log(err) 202 | Expect(err).Should(BeNil()) 203 | Expect(taskList).ShouldNot(BeNil()) 204 | Expect(taskList.Items).Should(ContainElement(*task)) 205 | 206 | mockTask = createMockTask("DELETE_ZONE", "COMPLETED") 207 | server.SetResponseJson(200, mockTask) 208 | _, err = client.Zones.Delete(task.Entity.ID) 209 | 210 | GinkgoT().Log(err) 211 | Expect(err).Should(BeNil()) 212 | }) 213 | }) 214 | }) 215 | }) 216 | -------------------------------------------------------------------------------- /photon/lightwave/oidcclient.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package lightwave 11 | 12 | import ( 13 | "crypto/tls" 14 | "crypto/x509" 15 | "encoding/json" 16 | "encoding/pem" 17 | "fmt" 18 | "io/ioutil" 19 | "log" 20 | "net/http" 21 | "net/url" 22 | "strings" 23 | ) 24 | 25 | const tokenScope string = "openid offline_access" 26 | 27 | type OIDCClient struct { 28 | httpClient *http.Client 29 | logger *log.Logger 30 | 31 | Endpoint string 32 | Options *OIDCClientOptions 33 | } 34 | 35 | type OIDCClientOptions struct { 36 | // Whether or not to ignore any TLS errors when talking to photon, 37 | // false by default. 38 | IgnoreCertificate bool 39 | 40 | // List of root CA's to use for server validation 41 | // nil by default. 42 | RootCAs *x509.CertPool 43 | 44 | // The scope values to use when requesting tokens 45 | TokenScope string 46 | } 47 | 48 | func NewOIDCClient(endpoint string, options *OIDCClientOptions, logger *log.Logger) (c *OIDCClient) { 49 | if logger == nil { 50 | logger = log.New(ioutil.Discard, "", log.LstdFlags) 51 | } 52 | 53 | options = buildOptions(options) 54 | tr := &http.Transport{ 55 | TLSClientConfig: &tls.Config{ 56 | InsecureSkipVerify: options.IgnoreCertificate, 57 | RootCAs: options.RootCAs}, 58 | } 59 | 60 | c = &OIDCClient{ 61 | httpClient: &http.Client{Transport: tr}, 62 | logger: logger, 63 | 64 | Endpoint: strings.TrimRight(endpoint, "/"), 65 | Options: options, 66 | } 67 | return 68 | } 69 | 70 | func buildOptions(options *OIDCClientOptions) (result *OIDCClientOptions) { 71 | result = &OIDCClientOptions{ 72 | TokenScope: tokenScope, 73 | } 74 | 75 | if options == nil { 76 | return 77 | } 78 | 79 | result.IgnoreCertificate = options.IgnoreCertificate 80 | 81 | if options.RootCAs != nil { 82 | result.RootCAs = options.RootCAs 83 | } 84 | 85 | if options.TokenScope != "" { 86 | result.TokenScope = options.TokenScope 87 | } 88 | 89 | return 90 | } 91 | 92 | func (client *OIDCClient) buildUrl(path string) (url string) { 93 | return fmt.Sprintf("%s%s", client.Endpoint, path) 94 | } 95 | 96 | // Cert download helper 97 | 98 | const certDownloadPath string = "/afd/vecs/ssl" 99 | 100 | type lightWaveCert struct { 101 | Value string `json:"encoded"` 102 | } 103 | 104 | func (client *OIDCClient) GetRootCerts() (certList []*x509.Certificate, err error) { 105 | // turn TLS verification off for 106 | originalTr := client.httpClient.Transport 107 | defer client.setTransport(originalTr) 108 | 109 | tr := &http.Transport{ 110 | TLSClientConfig: &tls.Config{ 111 | InsecureSkipVerify: true, 112 | }, 113 | } 114 | client.setTransport(tr) 115 | 116 | // get the certs 117 | resp, err := client.httpClient.Get(client.buildUrl(certDownloadPath)) 118 | if err != nil { 119 | return 120 | } 121 | defer resp.Body.Close() 122 | if resp.StatusCode != 200 { 123 | err = fmt.Errorf("Unexpected error retrieving auth server certs: %v %s", resp.StatusCode, resp.Status) 124 | return 125 | } 126 | 127 | // parse the certs 128 | certsData := &[]lightWaveCert{} 129 | err = json.NewDecoder(resp.Body).Decode(certsData) 130 | if err != nil { 131 | return 132 | } 133 | 134 | certList = make([]*x509.Certificate, len(*certsData)) 135 | for idx, cert := range *certsData { 136 | block, _ := pem.Decode([]byte(cert.Value)) 137 | if block == nil { 138 | err = fmt.Errorf("Unexpected response format: %v", certsData) 139 | return nil, err 140 | } 141 | 142 | decodedCert, err := x509.ParseCertificate(block.Bytes) 143 | if err != nil { 144 | return nil, err 145 | } 146 | 147 | certList[idx] = decodedCert 148 | } 149 | 150 | return 151 | } 152 | 153 | func (client *OIDCClient) setTransport(tr http.RoundTripper) { 154 | client.httpClient.Transport = tr 155 | } 156 | 157 | // Toke request helpers 158 | 159 | const tokenPath string = "/openidconnect/token" 160 | const passwordGrantFormatString = "grant_type=password&username=%s&password=%s&scope=%s" 161 | const refreshTokenGrantFormatString = "grant_type=refresh_token&refresh_token=%s" 162 | const clientGrantFormatString = "grant_type=password&username=%s&password=%s&scope=%s&client_id=%s" 163 | 164 | type OIDCTokenResponse struct { 165 | AccessToken string `json:"access_token"` 166 | ExpiresIn int `json:"expires_in"` 167 | RefreshToken string `json:"refresh_token,omitempty"` 168 | IdToken string `json:"id_token"` 169 | TokenType string `json:"token_type"` 170 | } 171 | 172 | func (client *OIDCClient) GetTokenByPasswordGrant(username string, password string) (tokens *OIDCTokenResponse, err error) { 173 | username = url.QueryEscape(username) 174 | password = url.QueryEscape(password) 175 | body := fmt.Sprintf(passwordGrantFormatString, username, password, client.Options.TokenScope) 176 | return client.getToken(body) 177 | } 178 | 179 | func (client *OIDCClient) GetClientTokenByPasswordGrant(username string, password string, clientID string) (tokens *OIDCTokenResponse, err error) { 180 | username = url.QueryEscape(username) 181 | password = url.QueryEscape(password) 182 | clientID = url.QueryEscape(clientID) 183 | body := fmt.Sprintf(clientGrantFormatString, username, password, client.Options.TokenScope, clientID) 184 | return client.getToken(body) 185 | } 186 | 187 | func (client *OIDCClient) GetTokenByRefreshTokenGrant(refreshToken string) (tokens *OIDCTokenResponse, err error) { 188 | body := fmt.Sprintf(refreshTokenGrantFormatString, refreshToken) 189 | return client.getToken(body) 190 | } 191 | 192 | func (client *OIDCClient) getToken(body string) (tokens *OIDCTokenResponse, err error) { 193 | request, err := http.NewRequest("POST", client.buildUrl(tokenPath), strings.NewReader(body)) 194 | if err != nil { 195 | return nil, err 196 | } 197 | request.Header.Add("Content-Type", "application/x-www-form-urlencoded") 198 | 199 | resp, err := client.httpClient.Do(request) 200 | if err != nil { 201 | return nil, err 202 | } 203 | defer resp.Body.Close() 204 | 205 | err = client.checkResponse(resp) 206 | if err != nil { 207 | return nil, err 208 | } 209 | 210 | tokens = &OIDCTokenResponse{} 211 | err = json.NewDecoder(resp.Body).Decode(tokens) 212 | if err != nil { 213 | return nil, err 214 | } 215 | 216 | return 217 | } 218 | 219 | type OIDCError struct { 220 | Code string `json:"error"` 221 | Message string `json:"error_description"` 222 | } 223 | 224 | func (e OIDCError) Error() string { 225 | return fmt.Sprintf("%v: %v", e.Code, e.Message) 226 | } 227 | 228 | func (client *OIDCClient) checkResponse(response *http.Response) (err error) { 229 | if response.StatusCode/100 == 2 { 230 | return 231 | } 232 | 233 | respBody, readErr := ioutil.ReadAll(response.Body) 234 | if err != nil { 235 | return fmt.Errorf( 236 | "Status: %v, Body: %v [%v]", response.Status, string(respBody[:]), readErr) 237 | } 238 | 239 | var oidcErr OIDCError 240 | err = json.Unmarshal(respBody, &oidcErr) 241 | if err != nil { 242 | return fmt.Errorf( 243 | "Status: %v, Body: %v [%v]", response.Status, string(respBody[:]), readErr) 244 | } 245 | 246 | return oidcErr 247 | } 248 | -------------------------------------------------------------------------------- /photon/helpers_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "strings" 14 | 15 | . "github.com/onsi/ginkgo" 16 | . "github.com/onsi/gomega" 17 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 18 | ) 19 | 20 | func hasStep(task *Task, operation, state string) bool { 21 | for _, step := range task.Steps { 22 | if step.State == state && step.Operation == operation { 23 | return true 24 | } 25 | } 26 | return false 27 | } 28 | 29 | // create mock quota instance 30 | func createMockQuota() Quota { 31 | mockQuota := Quota{ 32 | QuotaLineItems: map[string]QuotaStatusLineItem{ 33 | "vmCpu": {Unit: "COUNT", Limit: 100, Usage: 0}, 34 | "vmMemory": {Unit: "GB", Limit: 180, Usage: 0}, 35 | "diskCapacity": {Unit: "GB", Limit: 1000, Usage: 0}, 36 | }, 37 | } 38 | return mockQuota 39 | } 40 | 41 | func createTenant(server *mocks.Server, client *Client) string { 42 | mockTask := createMockTask("CREATE_TENANT", "COMPLETED") 43 | server.SetResponseJson(200, mockTask) 44 | tenantSpec := &TenantCreateSpec{ 45 | Name: randomString(10, "go-sdk-tenant-"), 46 | ResourceQuota: createMockQuota(), 47 | } 48 | task, err := client.Tenants.Create(tenantSpec) 49 | GinkgoT().Log(err) 50 | Expect(err).Should(BeNil()) 51 | return task.Entity.ID 52 | } 53 | 54 | // Checks the list of tenants and deletes the ones created by go-sdk 55 | func cleanTenants(client *Client) { 56 | tenants, err := client.Tenants.GetAll() 57 | if err != nil { 58 | GinkgoT().Log(err) 59 | } 60 | if tenants == nil { 61 | return 62 | } 63 | for _, tenant := range tenants.Items { 64 | if strings.HasPrefix(tenant.Name, "go-sdk-tenant-") { 65 | cleanProjects(client, tenant.ID) 66 | _, err := client.Tenants.Delete(tenant.ID) 67 | if err != nil { 68 | GinkgoT().Log(err) 69 | } 70 | } 71 | } 72 | } 73 | 74 | func createProject(server *mocks.Server, client *Client, tenantID string) string { 75 | mockTask := createMockTask("CREATE_PROJECT", "COMPLETED") 76 | server.SetResponseJson(200, mockTask) 77 | projSpec := &ProjectCreateSpec{ 78 | Name: randomString(10, "go-sdk-project-"), 79 | ResourceQuota: createMockQuota(), 80 | } 81 | task, err := client.Tenants.CreateProject(tenantID, projSpec) 82 | GinkgoT().Log(err) 83 | Expect(err).Should(BeNil()) 84 | return task.Entity.ID 85 | } 86 | 87 | func createRouter(server *mocks.Server, client *Client, projID string) string { 88 | mockTask := createMockTask("CREATE_ROUTER", "COMPLETED") 89 | server.SetResponseJson(200, mockTask) 90 | routerSpec := &RouterCreateSpec{ 91 | Name: randomString(10, "go-sdk-project-"), 92 | PrivateIpCidr: randomString(10, "go-sdk-project-cidr-"), 93 | } 94 | task, err := client.Projects.CreateRouter(projID, routerSpec) 95 | GinkgoT().Log(err) 96 | Expect(err).Should(BeNil()) 97 | return task.Entity.ID 98 | } 99 | 100 | // Checks the projects for the tenant and deletes ones created by go-sdk 101 | func cleanProjects(client *Client, tenantID string) { 102 | projList, err := client.Tenants.GetProjects(tenantID, &ProjectGetOptions{}) 103 | if err != nil { 104 | GinkgoT().Log(err) 105 | } 106 | if projList == nil { 107 | return 108 | } 109 | for _, proj := range projList.Items { 110 | if strings.HasPrefix(proj.Name, "go-sdk-project-") { 111 | _, err := client.Projects.Delete(proj.ID) 112 | if err != nil { 113 | GinkgoT().Log(err) 114 | } 115 | } 116 | } 117 | } 118 | 119 | // Returns flavorName, flavorID 120 | func createFlavor(server *mocks.Server, client *Client) (string, string) { 121 | mockTask := createMockTask("CREATE_FLAVOR", "COMPLETED") 122 | server.SetResponseJson(200, mockTask) 123 | flavorName := randomString(10, "go-sdk-flavor-") 124 | flavorSpec := &FlavorCreateSpec{ 125 | []QuotaLineItem{QuotaLineItem{"COUNT", 1, "persistent-disk.cost"}}, 126 | "persistent-disk", 127 | flavorName, 128 | } 129 | task, err := client.Flavors.Create(flavorSpec) 130 | GinkgoT().Log(err) 131 | Expect(err).Should(BeNil()) 132 | return flavorName, task.Entity.ID 133 | } 134 | 135 | func cleanFlavors(client *Client) { 136 | flavorList, err := client.Flavors.GetAll(&FlavorGetOptions{}) 137 | if err != nil { 138 | GinkgoT().Log(err) 139 | } 140 | for _, flavor := range flavorList.Items { 141 | if strings.HasPrefix(flavor.Name, "go-sdk-flavor-") { 142 | _, err := client.Flavors.Delete(flavor.ID) 143 | if err != nil { 144 | GinkgoT().Log(err) 145 | } 146 | } 147 | } 148 | } 149 | 150 | func cleanDisks(client *Client, projID string) { 151 | diskList, err := client.Projects.GetDisks(projID, &DiskGetOptions{}) 152 | if err != nil { 153 | GinkgoT().Log(err) 154 | } 155 | for _, disk := range diskList.Items { 156 | if strings.HasPrefix(disk.Name, "go-sdk-disk-") { 157 | task, err := client.Disks.Delete(disk.ID) 158 | task, err = client.Tasks.Wait(task.ID) 159 | if err != nil { 160 | GinkgoT().Log(err) 161 | } 162 | } 163 | } 164 | } 165 | 166 | func createImage(server *mocks.Server, client *Client) string { 167 | mockTask := createMockTask("CREATE_IMAGE", "COMPLETED", createMockStep("UPLOAD_IMAGE", "COMPLETED")) 168 | server.SetResponseJson(200, mockTask) 169 | 170 | // create image from file 171 | imagePath := "../testdata/tty_tiny.ova" 172 | task, err := client.Images.CreateFromFile(imagePath, &ImageCreateOptions{ReplicationType: "ON_DEMAND"}) 173 | task, err = client.Tasks.Wait(task.ID) 174 | 175 | GinkgoT().Log(err) 176 | Expect(err).Should(BeNil()) 177 | 178 | return task.Entity.ID 179 | } 180 | 181 | func cleanImages(client *Client) { 182 | imageList, err := client.Images.GetAll(&ImageGetOptions{}) 183 | if err != nil { 184 | GinkgoT().Log(err) 185 | } 186 | if imageList == nil { 187 | return 188 | } 189 | for _, image := range imageList.Items { 190 | if image.Name == "tty_tiny.ova" { 191 | task, err := client.Images.Delete(image.ID) 192 | task, err = client.Tasks.Wait(task.ID) 193 | if err != nil { 194 | GinkgoT().Log(err) 195 | } 196 | } 197 | } 198 | } 199 | 200 | func cleanVMs(client *Client, projID string) { 201 | vmList, err := client.Projects.GetVMs(projID, &VmGetOptions{}) 202 | if err != nil { 203 | GinkgoT().Log(err) 204 | } 205 | for _, vm := range vmList.Items { 206 | if strings.HasPrefix(vm.Name, "go-sdk-vm-") { 207 | task, err := client.VMs.Delete(vm.ID) 208 | task, err = client.Tasks.Wait(task.ID) 209 | if err != nil { 210 | GinkgoT().Log(err) 211 | } 212 | } 213 | } 214 | } 215 | 216 | func cleanHosts(client *Client) { 217 | hostList, err := client.InfraHosts.GetHosts() 218 | if err != nil { 219 | GinkgoT().Log(err) 220 | } 221 | for _, host := range hostList.Items { 222 | if host.Metadata != nil { 223 | if val, ok := host.Metadata["Test"]; ok && val == "go-sdk-host" { 224 | task, err := client.InfraHosts.Delete(host.ID) 225 | task, err = client.Tasks.Wait(task.ID) 226 | if err != nil { 227 | GinkgoT().Log(err) 228 | } 229 | } 230 | } 231 | } 232 | } 233 | 234 | func cleanSubnets(client *Client) { 235 | subnets, err := client.Subnets.GetAll(&SubnetGetOptions{}) 236 | if err != nil { 237 | GinkgoT().Log(err) 238 | } 239 | for _, subnet := range subnets.Items { 240 | if strings.HasPrefix(subnet.Name, "go-sdk-network-") { 241 | task, err := client.Subnets.Delete(subnet.ID) 242 | task, err = client.Tasks.Wait(task.ID) 243 | if err != nil { 244 | GinkgoT().Log(err) 245 | } 246 | } 247 | } 248 | } 249 | 250 | func cleanServices(client *Client, projID string) { 251 | services, err := client.Projects.GetServices(projID) 252 | if err != nil { 253 | GinkgoT().Log(err) 254 | } 255 | for _, service := range services.Items { 256 | if strings.HasPrefix(service.Name, "go-sdk-service-") { 257 | task, err := client.Services.Delete(service.ID) 258 | task, err = client.Tasks.Wait(task.ID) 259 | if err != nil { 260 | GinkgoT().Log(err) 261 | } 262 | } 263 | } 264 | } 265 | -------------------------------------------------------------------------------- /SSPI/sspi.go: -------------------------------------------------------------------------------- 1 | // +build windows 2 | 3 | package SSPI 4 | 5 | import ( 6 | "fmt" 7 | "strings" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | var ( 13 | secur32_dll = syscall.NewLazyDLL("secur32.dll") 14 | initSecurityInterface = secur32_dll.NewProc("InitSecurityInterfaceW") 15 | sec_fn *SecurityFunctionTable 16 | ) 17 | 18 | func init() { 19 | ptr, _, _ := initSecurityInterface.Call() 20 | sec_fn = (*SecurityFunctionTable)(unsafe.Pointer(ptr)) 21 | } 22 | 23 | const ( 24 | SEC_E_OK = 0 25 | SECPKG_CRED_OUTBOUND = 2 26 | SEC_WINNT_AUTH_IDENTITY_UNICODE = 2 27 | ISC_REQ_DELEGATE = 0x00000001 28 | ISC_REQ_REPLAY_DETECT = 0x00000004 29 | ISC_REQ_SEQUENCE_DETECT = 0x00000008 30 | ISC_REQ_CONFIDENTIALITY = 0x00000010 31 | ISC_REQ_CONNECTION = 0x00000800 32 | SECURITY_NETWORK_DREP = 0 33 | SEC_I_CONTINUE_NEEDED = 0x00090312 34 | SEC_I_COMPLETE_NEEDED = 0x00090313 35 | SEC_I_COMPLETE_AND_CONTINUE = 0x00090314 36 | SECBUFFER_VERSION = 0 37 | SECBUFFER_TOKEN = 2 38 | NTLMBUF_LEN = 12000 39 | ) 40 | 41 | const ISC_REQ = ISC_REQ_CONFIDENTIALITY | 42 | ISC_REQ_REPLAY_DETECT | 43 | ISC_REQ_SEQUENCE_DETECT | 44 | ISC_REQ_CONNECTION | 45 | ISC_REQ_DELEGATE 46 | 47 | type SecurityFunctionTable struct { 48 | dwVersion uint32 49 | EnumerateSecurityPackages uintptr 50 | QueryCredentialsAttributes uintptr 51 | AcquireCredentialsHandle uintptr 52 | FreeCredentialsHandle uintptr 53 | Reserved2 uintptr 54 | InitializeSecurityContext uintptr 55 | AcceptSecurityContext uintptr 56 | CompleteAuthToken uintptr 57 | DeleteSecurityContext uintptr 58 | ApplyControlToken uintptr 59 | QueryContextAttributes uintptr 60 | ImpersonateSecurityContext uintptr 61 | RevertSecurityContext uintptr 62 | MakeSignature uintptr 63 | VerifySignature uintptr 64 | FreeContextBuffer uintptr 65 | QuerySecurityPackageInfo uintptr 66 | Reserved3 uintptr 67 | Reserved4 uintptr 68 | Reserved5 uintptr 69 | Reserved6 uintptr 70 | Reserved7 uintptr 71 | Reserved8 uintptr 72 | QuerySecurityContextToken uintptr 73 | EncryptMessage uintptr 74 | DecryptMessage uintptr 75 | } 76 | 77 | type SEC_WINNT_AUTH_IDENTITY struct { 78 | User *uint16 79 | UserLength uint32 80 | Domain *uint16 81 | DomainLength uint32 82 | Password *uint16 83 | PasswordLength uint32 84 | Flags uint32 85 | } 86 | 87 | type TimeStamp struct { 88 | LowPart uint32 89 | HighPart int32 90 | } 91 | 92 | type SecHandle struct { 93 | dwLower uintptr 94 | dwUpper uintptr 95 | } 96 | 97 | type SecBuffer struct { 98 | cbBuffer uint32 99 | BufferType uint32 100 | pvBuffer *byte 101 | } 102 | 103 | type SecBufferDesc struct { 104 | ulVersion uint32 105 | cBuffers uint32 106 | pBuffers *SecBuffer 107 | } 108 | 109 | type SSPIAuth struct { 110 | Domain string 111 | UserName string 112 | Password string 113 | Service string 114 | cred SecHandle 115 | ctxt SecHandle 116 | } 117 | 118 | type Auth interface { 119 | InitialBytes() ([]byte, error) 120 | NextBytes([]byte) ([]byte, error) 121 | Free() 122 | } 123 | 124 | // GetAuth returns SSPI auth object initialized with given params and true for success 125 | // In case of error, it will return nil SSPI object and false for failure 126 | func GetAuth(user, password, service, workstation string) (Auth, bool) { 127 | if user == "" { 128 | return &SSPIAuth{Service: service}, true 129 | } 130 | if !strings.ContainsRune(user, '\\') { 131 | return nil, false 132 | } 133 | domain_user := strings.SplitN(user, "\\", 2) 134 | return &SSPIAuth{ 135 | Domain: domain_user[0], 136 | UserName: domain_user[1], 137 | Password: password, 138 | Service: service, 139 | }, true 140 | } 141 | 142 | func (auth *SSPIAuth) InitialBytes() ([]byte, error) { 143 | var identity *SEC_WINNT_AUTH_IDENTITY 144 | if auth.UserName != "" { 145 | identity = &SEC_WINNT_AUTH_IDENTITY{ 146 | Flags: SEC_WINNT_AUTH_IDENTITY_UNICODE, 147 | Password: syscall.StringToUTF16Ptr(auth.Password), 148 | PasswordLength: uint32(len(auth.Password)), 149 | Domain: syscall.StringToUTF16Ptr(auth.Domain), 150 | DomainLength: uint32(len(auth.Domain)), 151 | User: syscall.StringToUTF16Ptr(auth.UserName), 152 | UserLength: uint32(len(auth.UserName)), 153 | } 154 | } 155 | var ts TimeStamp 156 | sec_ok, _, _ := syscall.Syscall9(sec_fn.AcquireCredentialsHandle, 157 | 9, 158 | 0, 159 | uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr("Negotiate"))), 160 | SECPKG_CRED_OUTBOUND, 161 | 0, 162 | uintptr(unsafe.Pointer(identity)), 163 | 0, 164 | 0, 165 | uintptr(unsafe.Pointer(&auth.cred)), 166 | uintptr(unsafe.Pointer(&ts))) 167 | if sec_ok != SEC_E_OK { 168 | return nil, fmt.Errorf("AcquireCredentialsHandle failed %x", sec_ok) 169 | } 170 | 171 | var buf SecBuffer 172 | var desc SecBufferDesc 173 | desc.ulVersion = SECBUFFER_VERSION 174 | desc.cBuffers = 1 175 | desc.pBuffers = &buf 176 | 177 | outbuf := make([]byte, NTLMBUF_LEN) 178 | buf.cbBuffer = NTLMBUF_LEN 179 | buf.BufferType = SECBUFFER_TOKEN 180 | buf.pvBuffer = &outbuf[0] 181 | 182 | var attrs uint32 183 | sec_ok, _, _ = syscall.Syscall12(sec_fn.InitializeSecurityContext, 184 | 12, 185 | uintptr(unsafe.Pointer(&auth.cred)), 186 | 0, 187 | uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(auth.Service))), 188 | ISC_REQ, 189 | 0, 190 | SECURITY_NETWORK_DREP, 191 | 0, 192 | 0, 193 | uintptr(unsafe.Pointer(&auth.ctxt)), 194 | uintptr(unsafe.Pointer(&desc)), 195 | uintptr(unsafe.Pointer(&attrs)), 196 | uintptr(unsafe.Pointer(&ts))) 197 | if sec_ok == SEC_I_COMPLETE_AND_CONTINUE || 198 | sec_ok == SEC_I_COMPLETE_NEEDED { 199 | syscall.Syscall6(sec_fn.CompleteAuthToken, 200 | 2, 201 | uintptr(unsafe.Pointer(&auth.ctxt)), 202 | uintptr(unsafe.Pointer(&desc)), 203 | 0, 0, 0, 0) 204 | } else if sec_ok != SEC_E_OK && 205 | sec_ok != SEC_I_CONTINUE_NEEDED { 206 | syscall.Syscall6(sec_fn.FreeCredentialsHandle, 207 | 1, 208 | uintptr(unsafe.Pointer(&auth.cred)), 209 | 0, 0, 0, 0, 0) 210 | return nil, fmt.Errorf("InitialBytes InitializeSecurityContext failed %x", sec_ok) 211 | } 212 | return outbuf[:buf.cbBuffer], nil 213 | } 214 | 215 | func (auth *SSPIAuth) NextBytes(bytes []byte) ([]byte, error) { 216 | var in_buf, out_buf SecBuffer 217 | var in_desc, out_desc SecBufferDesc 218 | 219 | in_desc.ulVersion = SECBUFFER_VERSION 220 | in_desc.cBuffers = 1 221 | in_desc.pBuffers = &in_buf 222 | 223 | out_desc.ulVersion = SECBUFFER_VERSION 224 | out_desc.cBuffers = 1 225 | out_desc.pBuffers = &out_buf 226 | 227 | in_buf.BufferType = SECBUFFER_TOKEN 228 | in_buf.pvBuffer = &bytes[0] 229 | in_buf.cbBuffer = uint32(len(bytes)) 230 | 231 | outbuf := make([]byte, NTLMBUF_LEN) 232 | out_buf.BufferType = SECBUFFER_TOKEN 233 | out_buf.pvBuffer = &outbuf[0] 234 | out_buf.cbBuffer = NTLMBUF_LEN 235 | 236 | var attrs uint32 237 | var ts TimeStamp 238 | sec_ok, _, _ := syscall.Syscall12(sec_fn.InitializeSecurityContext, 239 | 12, 240 | uintptr(unsafe.Pointer(&auth.cred)), 241 | uintptr(unsafe.Pointer(&auth.ctxt)), 242 | uintptr(unsafe.Pointer(syscall.StringToUTF16Ptr(auth.Service))), 243 | ISC_REQ, 244 | 0, 245 | SECURITY_NETWORK_DREP, 246 | uintptr(unsafe.Pointer(&in_desc)), 247 | 0, 248 | uintptr(unsafe.Pointer(&auth.ctxt)), 249 | uintptr(unsafe.Pointer(&out_desc)), 250 | uintptr(unsafe.Pointer(&attrs)), 251 | uintptr(unsafe.Pointer(&ts))) 252 | if sec_ok == SEC_I_COMPLETE_AND_CONTINUE || 253 | sec_ok == SEC_I_COMPLETE_NEEDED { 254 | syscall.Syscall6(sec_fn.CompleteAuthToken, 255 | 2, 256 | uintptr(unsafe.Pointer(&auth.ctxt)), 257 | uintptr(unsafe.Pointer(&out_desc)), 258 | 0, 0, 0, 0) 259 | } else if sec_ok != SEC_E_OK && 260 | sec_ok != SEC_I_CONTINUE_NEEDED { 261 | return nil, fmt.Errorf("NextBytes InitializeSecurityContext failed %x", sec_ok) 262 | } 263 | 264 | return outbuf[:out_buf.cbBuffer], nil 265 | } 266 | 267 | func (auth *SSPIAuth) Free() { 268 | syscall.Syscall6(sec_fn.DeleteSecurityContext, 269 | 1, 270 | uintptr(unsafe.Pointer(&auth.ctxt)), 271 | 0, 0, 0, 0, 0) 272 | syscall.Syscall6(sec_fn.FreeCredentialsHandle, 273 | 1, 274 | uintptr(unsafe.Pointer(&auth.cred)), 275 | 0, 0, 0, 0, 0) 276 | } -------------------------------------------------------------------------------- /photon/subnets_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = Describe("Subnet", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | subnetCreateSpec *SubnetCreateSpec 23 | subnetSpecWithPortGroups *SubnetCreateSpec 24 | portGroups *PortGroups 25 | tenantID string 26 | projID string 27 | routerID string 28 | ) 29 | 30 | BeforeEach(func() { 31 | server, client = testSetup() 32 | tenantID = createTenant(server, client) 33 | projID = createProject(server, client, tenantID) 34 | routerID = createRouter(server, client, projID) 35 | portGroups = &PortGroups{Names: []string{"portGroup"}} 36 | subnetCreateSpec = &SubnetCreateSpec{Name: "subnet-1", Description: "Test subnet", PrivateIpCidr: "cidr1"} 37 | subnetSpecWithPortGroups = &SubnetCreateSpec{Name: "subnet-1", Description: "Test subnet", PrivateIpCidr: "cidr1"} 38 | }) 39 | 40 | AfterEach(func() { 41 | cleanSubnets(client) 42 | cleanTenants(client) 43 | server.Close() 44 | }) 45 | 46 | Describe("CreateDeleteSubnet", func() { 47 | It("Subnet create and delete succeeds", func() { 48 | mockTask := createMockTask("CREATE_SUBNET", "COMPLETED") 49 | server.SetResponseJson(200, mockTask) 50 | 51 | task, err := client.Routers.CreateSubnet(routerID, subnetCreateSpec) 52 | task, err = client.Tasks.Wait(task.ID) 53 | GinkgoT().Log(err) 54 | Expect(err).Should(BeNil()) 55 | Expect(task).ShouldNot(BeNil()) 56 | Expect(task.Operation).Should(Equal("CREATE_SUBNET")) 57 | Expect(task.State).Should(Equal("COMPLETED")) 58 | 59 | mockTask = createMockTask("DELETE_SUBNET", "COMPLETED") 60 | server.SetResponseJson(200, mockTask) 61 | task, err = client.Subnets.Delete("subnet-Id") 62 | task, err = client.Tasks.Wait(task.ID) 63 | GinkgoT().Log(err) 64 | Expect(err).Should(BeNil()) 65 | Expect(task).ShouldNot(BeNil()) 66 | Expect(task.Operation).Should(Equal("DELETE_SUBNET")) 67 | Expect(task.State).Should(Equal("COMPLETED")) 68 | }) 69 | }) 70 | 71 | Describe("CreateDeletePortGroup", func() { 72 | It("PortGroup create and delete succeeds", func() { 73 | mockTask := createMockTask("CREATE_PORT_GROUP", "COMPLETED") 74 | server.SetResponseJson(200, mockTask) 75 | 76 | task, err := client.Subnets.Create(subnetSpecWithPortGroups) 77 | task, err = client.Tasks.Wait(task.ID) 78 | GinkgoT().Log(err) 79 | Expect(err).Should(BeNil()) 80 | Expect(task).ShouldNot(BeNil()) 81 | Expect(task.Operation).Should(Equal("CREATE_PORT_GROUP")) 82 | Expect(task.State).Should(Equal("COMPLETED")) 83 | 84 | mockTask = createMockTask("DELETE_PORT_GROUP", "COMPLETED") 85 | server.SetResponseJson(200, mockTask) 86 | task, err = client.Subnets.Delete(task.Entity.ID) 87 | task, err = client.Tasks.Wait(task.ID) 88 | GinkgoT().Log(err) 89 | Expect(err).Should(BeNil()) 90 | Expect(task).ShouldNot(BeNil()) 91 | Expect(task.Operation).Should(Equal("DELETE_PORT_GROUP")) 92 | Expect(task.State).Should(Equal("COMPLETED")) 93 | }) 94 | }) 95 | 96 | Describe("GetSubnet", func() { 97 | It("Get returns subnet", func() { 98 | mockTask := createMockTask("CREATE_SUBNET", "COMPLETED") 99 | server.SetResponseJson(200, mockTask) 100 | 101 | task, err := client.Routers.CreateSubnet(routerID, subnetCreateSpec) 102 | task, err = client.Tasks.Wait(task.ID) 103 | GinkgoT().Log(err) 104 | Expect(err).Should(BeNil()) 105 | Expect(task).ShouldNot(BeNil()) 106 | Expect(task.Operation).Should(Equal("CREATE_SUBNET")) 107 | Expect(task.State).Should(Equal("COMPLETED")) 108 | 109 | server.SetResponseJson(200, Subnet{Name: "subnet-1", Description: "Test subnet", PrivateIpCidr: "cidr1"}) 110 | subnet, err := client.Subnets.Get(task.Entity.ID) 111 | 112 | GinkgoT().Log(err) 113 | Expect(err).Should(BeNil()) 114 | Expect(subnet).ShouldNot(BeNil()) 115 | 116 | var found bool 117 | if subnet.Name == "subnet-1" && subnet.ID == task.Entity.ID { 118 | found = true 119 | } 120 | Expect(found).Should(BeTrue()) 121 | 122 | mockTask = createMockTask("DELETE_SUBNET", "COMPLETED") 123 | server.SetResponseJson(200, mockTask) 124 | _, err = client.Subnets.Delete(task.Entity.ID) 125 | 126 | GinkgoT().Log(err) 127 | Expect(err).Should(BeNil()) 128 | }) 129 | 130 | It("GetAll Subnet succeeds", func() { 131 | mockTask := createMockTask("CREATE_SUBNET", "COMPLETED") 132 | server.SetResponseJson(200, mockTask) 133 | task, err := client.Subnets.Create(subnetSpecWithPortGroups) 134 | task, err = client.Tasks.Wait(task.ID) 135 | GinkgoT().Log(err) 136 | Expect(err).Should(BeNil()) 137 | 138 | server.SetResponseJson(200, createMockSubnetsPage(Subnet{Name: subnetSpecWithPortGroups.Name})) 139 | subnets, err := client.Subnets.GetAll(&SubnetGetOptions{}) 140 | 141 | GinkgoT().Log(err) 142 | Expect(err).Should(BeNil()) 143 | Expect(subnets).ShouldNot(BeNil()) 144 | 145 | var found bool 146 | for _, subnet := range subnets.Items { 147 | if subnet.Name == subnetSpecWithPortGroups.Name && subnet.ID == task.Entity.ID { 148 | found = true 149 | break 150 | } 151 | } 152 | Expect(found).Should(BeTrue()) 153 | 154 | mockTask = createMockTask("DELETE_SUBNET", "COMPLETED") 155 | server.SetResponseJson(200, mockTask) 156 | task, err = client.Subnets.Delete(task.Entity.ID) 157 | task, err = client.Tasks.Wait(task.ID) 158 | GinkgoT().Log(err) 159 | Expect(err).Should(BeNil()) 160 | }) 161 | 162 | It("GetAll PortGroups with optional name succeeds", func() { 163 | mockTask := createMockTask("CREATE_PORT_GROUP", "COMPLETED") 164 | server.SetResponseJson(200, mockTask) 165 | task, err := client.Subnets.Create(subnetSpecWithPortGroups) 166 | task, err = client.Tasks.Wait(task.ID) 167 | GinkgoT().Log(err) 168 | Expect(err).Should(BeNil()) 169 | 170 | server.SetResponseJson(200, createMockSubnetsPage(Subnet{Name: subnetSpecWithPortGroups.Name})) 171 | subnets, err := client.Subnets.GetAll(&SubnetGetOptions{Name: subnetSpecWithPortGroups.Name}) 172 | 173 | GinkgoT().Log(err) 174 | Expect(err).Should(BeNil()) 175 | Expect(subnets).ShouldNot(BeNil()) 176 | 177 | var found bool 178 | for _, subnet := range subnets.Items { 179 | if subnet.Name == subnetSpecWithPortGroups.Name && subnet.ID == task.Entity.ID { 180 | found = true 181 | break 182 | } 183 | } 184 | Expect(len(subnets.Items)).Should(Equal(1)) 185 | Expect(found).Should(BeTrue()) 186 | 187 | mockTask = createMockTask("DELETE_PORT_GROUP", "COMPLETED") 188 | server.SetResponseJson(200, mockTask) 189 | task, err = client.Subnets.Delete(task.Entity.ID) 190 | task, err = client.Tasks.Wait(task.ID) 191 | GinkgoT().Log(err) 192 | Expect(err).Should(BeNil()) 193 | }) 194 | }) 195 | 196 | Describe("UpdateSubnet", func() { 197 | It("update subnet's name", func() { 198 | mockTask := createMockTask("UPDATE_SUBNET", "COMPLETED") 199 | server.SetResponseJson(200, mockTask) 200 | 201 | subnetSpec := &SubnetUpdateSpec{SubnetName: "subnet-1"} 202 | task, err := client.Subnets.Update("subnet-Id", subnetSpec) 203 | task, err = client.Tasks.Wait(task.ID) 204 | GinkgoT().Log(err) 205 | Expect(err).Should(BeNil()) 206 | Expect(task).ShouldNot(BeNil()) 207 | Expect(task.Operation).Should(Equal("UPDATE_SUBNET")) 208 | Expect(task.State).Should(Equal("COMPLETED")) 209 | }) 210 | }) 211 | 212 | Describe("SetDefaultSubnet", func() { 213 | It("Set default subnet succeeds", func() { 214 | mockTask := createMockTask("SET_DEFAULT_SUBNET", "COMPLETED") 215 | server.SetResponseJson(200, mockTask) 216 | 217 | task, err := client.Subnets.SetDefault("subnetId") 218 | GinkgoT().Log(err) 219 | Expect(err).Should(BeNil()) 220 | Expect(task).ShouldNot(BeNil()) 221 | Expect(task.Operation).Should(Equal("SET_DEFAULT_SUBNET")) 222 | Expect(task.State).Should(Equal("COMPLETED")) 223 | }) 224 | }) 225 | }) 226 | -------------------------------------------------------------------------------- /photon/mocks_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "crypto/tls" 15 | "log" 16 | "math/rand" 17 | "net/http" 18 | "os" 19 | "strconv" 20 | "time" 21 | 22 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 23 | ) 24 | 25 | type MockTasksPage struct { 26 | Items []Task `json:"items"` 27 | NextPageLink string `json:"nextPageLink"` 28 | PreviousPageLink string `json:"previousPageLink"` 29 | } 30 | 31 | type MockZonesPage struct { 32 | Items []Zone `json:"items"` 33 | NextPageLink string `json:"nextPageLink"` 34 | PreviousPageLink string `json:"previousPageLink"` 35 | } 36 | 37 | type MockProjectsPage struct { 38 | Items []ProjectCompact `json:"items"` 39 | NextPageLink string `json:"nextPageLink"` 40 | PreviousPageLink string `json:"previousPageLink"` 41 | } 42 | 43 | type MockTenantsPage struct { 44 | Items []Tenant `json:"items"` 45 | NextPageLink string `json:"nextPageLink"` 46 | PreviousPageLink string `json:"previousPageLink"` 47 | } 48 | 49 | type MockTenantPage struct { 50 | ID string `json:"id"` 51 | Name string `json:"name"` 52 | } 53 | 54 | type MockVmsPage struct { 55 | Items []VM `json:"items"` 56 | NextPageLink string `json:"nextPageLink"` 57 | PreviousPageLink string `json:"previousPageLink"` 58 | } 59 | 60 | type MockFlavorsPage struct { 61 | Items []Flavor `json:"items"` 62 | NextPageLink string `json:"nextPageLink"` 63 | PreviousPageLink string `json:"previousPageLink"` 64 | } 65 | 66 | type MockSubnetsPage struct { 67 | Items []Subnet `json:"items"` 68 | NextPageLink string `json:"nextPageLink"` 69 | PreviousPageLink string `json:"previousPageLink"` 70 | } 71 | 72 | type MockServicesPage struct { 73 | Items []Service `json:"items"` 74 | NextPageLink string `json:"nextPageLink"` 75 | PreviousPageLink string `json:"previousPageLink"` 76 | } 77 | 78 | type MockImagesPage struct { 79 | Items []Image `json:"items"` 80 | NextPageLink string `json:"nextPageLink"` 81 | PreviousPageLink string `json:"previousPageLink"` 82 | } 83 | 84 | type MockHostsPage struct { 85 | Items []Host `json:"items"` 86 | NextPageLink string `json:"nextPageLink"` 87 | PreviousPageLink string `json:"previousPageLink"` 88 | } 89 | 90 | func testSetup() (server *mocks.Server, client *Client) { 91 | // If TEST_ENDPOINT env var is set, return an empty server and point 92 | // the client to TEST_ENDPOINT. This lets us run tests as integration tests 93 | var uri string 94 | if os.Getenv("TEST_ENDPOINT") != "" { 95 | server = &mocks.Server{} 96 | uri = os.Getenv("TEST_ENDPOINT") 97 | } else { 98 | server = mocks.NewTestServer() 99 | uri = server.HttpServer.URL 100 | } 101 | 102 | options := &ClientOptions{ 103 | IgnoreCertificate: true, 104 | } 105 | transport := &http.Transport{ 106 | TLSClientConfig: &tls.Config{ 107 | InsecureSkipVerify: options.IgnoreCertificate, 108 | }, 109 | } 110 | 111 | httpClient := &http.Client{Transport: transport} 112 | if os.Getenv("API_ACCESS_TOKEN") != "" { 113 | options.TokenOptions.AccessToken = os.Getenv("API_ACCESS_TOKEN") 114 | } 115 | if os.Getenv("TEST_ENDPOINT") != "" && os.Getenv("API_ACCESS_TOKEN") == "" { 116 | username := os.Getenv("USERNAME") 117 | password := os.Getenv("PASSWORD") 118 | 119 | client = NewTestClient(uri, options, httpClient) 120 | tokens, err := client.Auth.GetTokensByPassword(username, password) 121 | if err != nil { 122 | log.Fatal(err) 123 | } 124 | options.TokenOptions = tokens 125 | } 126 | 127 | client = NewTestClient(uri, options, httpClient) 128 | return 129 | } 130 | 131 | func createMockStep(operation, state string) Step { 132 | return Step{State: state, Operation: operation} 133 | } 134 | 135 | func createMockTask(operation, state string, steps ...Step) *Task { 136 | return &Task{Operation: operation, State: state, Steps: steps} 137 | } 138 | 139 | func createMockTasksPage(tasks ...Task) *MockTasksPage { 140 | tasksPage := MockTasksPage{ 141 | Items: tasks, 142 | NextPageLink: "", 143 | PreviousPageLink: "", 144 | } 145 | 146 | return &tasksPage 147 | } 148 | 149 | func createMockZonesPage(zones ...Zone) *MockZonesPage { 150 | zonesPage := MockZonesPage{ 151 | Items: zones, 152 | NextPageLink: "", 153 | PreviousPageLink: "", 154 | } 155 | 156 | return &zonesPage 157 | } 158 | 159 | func createMockProjectsPage(projects ...ProjectCompact) *MockProjectsPage { 160 | projectsPage := MockProjectsPage{ 161 | Items: projects, 162 | NextPageLink: "", 163 | PreviousPageLink: "", 164 | } 165 | 166 | return &projectsPage 167 | } 168 | 169 | func createMockTenantsPage(tenants ...Tenant) *MockTenantsPage { 170 | tenantsPage := MockTenantsPage{ 171 | Items: tenants, 172 | NextPageLink: "", 173 | PreviousPageLink: "", 174 | } 175 | 176 | return &tenantsPage 177 | } 178 | 179 | func createMockTenantPage() *MockTenantPage { 180 | tenantPage := MockTenantPage{ 181 | ID: "12345", 182 | Name: "TestTenant", 183 | } 184 | return &tenantPage 185 | } 186 | 187 | func createMockVmsPage(vms ...VM) *MockVmsPage { 188 | vmsPage := MockVmsPage{ 189 | Items: vms, 190 | NextPageLink: "", 191 | PreviousPageLink: "", 192 | } 193 | 194 | return &vmsPage 195 | } 196 | 197 | func createMockFlavorsPage(flavors ...Flavor) *MockFlavorsPage { 198 | flavorsPage := MockFlavorsPage{ 199 | Items: flavors, 200 | NextPageLink: "", 201 | PreviousPageLink: "", 202 | } 203 | 204 | return &flavorsPage 205 | } 206 | 207 | func createMockSubnetsPage(subnets ...Subnet) *MockSubnetsPage { 208 | subnetsPage := MockSubnetsPage{ 209 | Items: subnets, 210 | NextPageLink: "", 211 | PreviousPageLink: "", 212 | } 213 | 214 | return &subnetsPage 215 | } 216 | 217 | func createMockServicesPage(services ...Service) *MockServicesPage { 218 | servicesPage := MockServicesPage{ 219 | Items: services, 220 | NextPageLink: "", 221 | PreviousPageLink: "", 222 | } 223 | 224 | return &servicesPage 225 | } 226 | 227 | func createMockImagesPage(images ...Image) *MockImagesPage { 228 | imagesPage := MockImagesPage{ 229 | Items: images, 230 | NextPageLink: "", 231 | PreviousPageLink: "", 232 | } 233 | 234 | return &imagesPage 235 | } 236 | 237 | func createMockHostsPage(hosts ...Host) *MockHostsPage { 238 | hostsPage := MockHostsPage{ 239 | Items: hosts, 240 | NextPageLink: "", 241 | PreviousPageLink: "", 242 | } 243 | 244 | return &hostsPage 245 | } 246 | 247 | func createMockApiError(code string, message string, httpStatusCode int) *ApiError { 248 | apiError := ApiError{ 249 | Code: code, 250 | Message: message, 251 | HttpStatusCode: httpStatusCode, 252 | } 253 | 254 | return &apiError 255 | } 256 | 257 | func createMockAuthInfo(server *mocks.Server) (mock *AuthInfo) { 258 | mock = &AuthInfo{ 259 | Endpoint: "", 260 | Port: 0, 261 | } 262 | 263 | if server == nil { 264 | return 265 | } 266 | 267 | address, port, err := server.GetAddressAndPort() 268 | if err != nil { 269 | return 270 | } 271 | 272 | mock.Endpoint = address 273 | mock.Port = port 274 | 275 | return 276 | } 277 | 278 | var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ") 279 | 280 | func randomString(n int, prefixes ...string) string { 281 | rand.Seed(time.Now().UTC().UnixNano()) 282 | b := make([]rune, n) 283 | for i := range b { 284 | b[i] = letters[rand.Intn(len(letters))] 285 | } 286 | 287 | var buffer bytes.Buffer 288 | 289 | for i := 0; i < len(prefixes); i++ { 290 | buffer.WriteString(prefixes[i]) 291 | } 292 | 293 | buffer.WriteString(string(b)) 294 | return buffer.String() 295 | } 296 | 297 | func randomAddress() string { 298 | rand.Seed(time.Now().UTC().UnixNano()) 299 | addr := strconv.Itoa(rand.Intn(256)) 300 | for i := 0; i < 3; i++ { 301 | addr += "." + strconv.Itoa(rand.Intn(256)) 302 | } 303 | return addr 304 | } 305 | 306 | func isRealAgent() bool { 307 | return os.Getenv("REAL_AGENT") != "" 308 | } 309 | 310 | func isIntegrationTest() bool { 311 | return os.Getenv("TEST_ENDPOINT") != "" 312 | } 313 | -------------------------------------------------------------------------------- /photon/system_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "fmt" 14 | . "github.com/onsi/ginkgo" 15 | . "github.com/onsi/gomega" 16 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 17 | ) 18 | 19 | var _ = Describe("System", func() { 20 | var ( 21 | server *mocks.Server 22 | client *Client 23 | ) 24 | 25 | BeforeEach(func() { 26 | if isIntegrationTest() { 27 | Skip("Skipping system test on integration mode.") 28 | } 29 | server, client = testSetup() 30 | }) 31 | 32 | AfterEach(func() { 33 | server.Close() 34 | }) 35 | 36 | // Tests system status 37 | Describe("GetSystemStatus", func() { 38 | It("GetStatus200", func() { 39 | expectedStruct := Status{"READY", []Component{{"PHOTON_CONTROLLER", "", "READY"}}} 40 | server.SetResponseJson(200, expectedStruct) 41 | 42 | status, err := client.System.GetSystemStatus() 43 | GinkgoT().Log(err) 44 | Expect(err).Should(BeNil()) 45 | Expect(status.Status).Should(Equal(expectedStruct.Status)) 46 | Expect(status.Components).Should(HaveLen(1)) 47 | }) 48 | }) 49 | 50 | // Tests system info 51 | Describe("GetSystemInfo", func() { 52 | It("GetInfoFromDeployment", func() { 53 | mockDeployment := SystemInfo{ 54 | ImageDatastores: []string{randomString(10, "go-sdk-deployment-")}, 55 | UseImageDatastoreForVms: true, 56 | Auth: &AuthInfo{}, 57 | NetworkConfiguration: &NetworkConfiguration{Enabled: false}, 58 | } 59 | server.SetResponseJson(200, mockDeployment) 60 | deployment, err := client.System.GetSystemInfo() 61 | 62 | GinkgoT().Log(err) 63 | Expect(err).Should(BeNil()) 64 | Expect(deployment).ShouldNot(BeNil()) 65 | Expect(deployment.ImageDatastores).Should(Equal(mockDeployment.ImageDatastores)) 66 | Expect(deployment.UseImageDatastoreForVms).Should(Equal(mockDeployment.UseImageDatastoreForVms)) 67 | }) 68 | }) 69 | 70 | // Tests pause and resume system 71 | Describe("PauseSystemAndPauseBackgroundTasks", func() { 72 | It("Pause System and Resume System succeeds", func() { 73 | mockTask := createMockTask("PAUSE_SYSTEM", "COMPLETED") 74 | server.SetResponseJson(200, mockTask) 75 | 76 | task, err := client.System.PauseSystem() 77 | task, err = client.Tasks.Wait(task.ID) 78 | 79 | GinkgoT().Log(err) 80 | Expect(err).Should(BeNil()) 81 | Expect(task).ShouldNot(BeNil()) 82 | Expect(task.Operation).Should(Equal("PAUSE_SYSTEM")) 83 | Expect(task.State).Should(Equal("COMPLETED")) 84 | 85 | mockTask = createMockTask("RESUME_SYSTEM", "COMPLETED") 86 | server.SetResponseJson(200, mockTask) 87 | 88 | task, err = client.System.ResumeSystem() 89 | task, err = client.Tasks.Wait(task.ID) 90 | 91 | GinkgoT().Log(err) 92 | Expect(err).Should(BeNil()) 93 | Expect(task).ShouldNot(BeNil()) 94 | Expect(task.Operation).Should(Equal("RESUME_SYSTEM")) 95 | Expect(task.State).Should(Equal("COMPLETED")) 96 | }) 97 | 98 | It("Pause Background Tasks and Resume System succeeds", func() { 99 | mockTask := createMockTask("PAUSE_BACKGROUND_TASKS", "COMPLETED") 100 | server.SetResponseJson(200, mockTask) 101 | 102 | task, err := client.System.PauseBackgroundTasks() 103 | task, err = client.Tasks.Wait(task.ID) 104 | 105 | GinkgoT().Log(err) 106 | Expect(err).Should(BeNil()) 107 | Expect(task).ShouldNot(BeNil()) 108 | Expect(task.Operation).Should(Equal("PAUSE_BACKGROUND_TASKS")) 109 | Expect(task.State).Should(Equal("COMPLETED")) 110 | 111 | mockTask = createMockTask("RESUME_SYSTEM", "COMPLETED") 112 | server.SetResponseJson(200, mockTask) 113 | 114 | task, err = client.System.ResumeSystem() 115 | task, err = client.Tasks.Wait(task.ID) 116 | 117 | GinkgoT().Log(err) 118 | Expect(err).Should(BeNil()) 119 | Expect(task).ShouldNot(BeNil()) 120 | Expect(task.Operation).Should(Equal("RESUME_SYSTEM")) 121 | Expect(task.State).Should(Equal("COMPLETED")) 122 | }) 123 | }) 124 | 125 | // Tests update to system security group 126 | Describe("SetSecurityGroups", func() { 127 | It("sets security groups for the system", func() { 128 | mockTask := createMockTask("SET_SECURITY_GROUPS", "COMPLETED") 129 | server.SetResponseJson(200, mockTask) 130 | 131 | // Set security groups for the system 132 | expected := &SystemInfo{ 133 | Auth: &AuthInfo{ 134 | SecurityGroups: []string{ 135 | randomString(10), 136 | randomString(10), 137 | }, 138 | }, 139 | } 140 | 141 | payload := SecurityGroupsSpec{ 142 | Items: expected.Auth.SecurityGroups, 143 | } 144 | updateTask, err := client.System.SetSecurityGroups(&payload) 145 | updateTask, err = client.Tasks.Wait(updateTask.ID) 146 | Expect(err).Should(BeNil()) 147 | 148 | // Get the security groups for the system 149 | server.SetResponseJson(200, expected) 150 | deployment, err := client.System.GetSystemInfo() 151 | Expect(err).Should(BeNil()) 152 | Expect(deployment.Auth.SecurityGroups).To(ContainElement(payload.Items[0])) 153 | Expect(deployment.Auth.SecurityGroups).To(ContainElement(payload.Items[1])) 154 | }) 155 | }) 156 | 157 | // Tests system size 158 | Describe("SystemSize", func() { 159 | It("GetSystemSize", func() { 160 | mockSize := &SystemUsage{ 161 | NumberDatastores: 1, 162 | NumberHosts: 1, 163 | NumberProjects: 0, 164 | NumberServices: 1, 165 | NumberTenants: 2, 166 | } 167 | server.SetResponseJson(200, mockSize) 168 | 169 | systemSize, err := client.System.GetSystemSize() 170 | GinkgoT().Log(err) 171 | Expect(err).Should(BeNil()) 172 | Expect(systemSize).ShouldNot(BeNil()) 173 | Expect(systemSize.NumberDatastores).Should(Equal(1)) 174 | Expect(systemSize.NumberHosts).Should(Equal(1)) 175 | Expect(systemSize.NumberProjects).Should(Equal(0)) 176 | Expect(systemSize.NumberServices).Should(Equal(1)) 177 | Expect(systemSize.NumberTenants).Should(Equal(2)) 178 | }) 179 | }) 180 | 181 | Describe("GetAuthInfo", func() { 182 | It("returns auth info", func() { 183 | expected := createMockAuthInfo(nil) 184 | server.SetResponseJson(200, expected) 185 | 186 | info, err := client.System.GetAuthInfo() 187 | fmt.Fprintf(GinkgoWriter, "Got auth info: %+v\n", info) 188 | Expect(err).Should(BeNil()) 189 | Expect(info).ShouldNot(BeNil()) 190 | Expect(info).Should(BeEquivalentTo(expected)) 191 | }) 192 | }) 193 | 194 | Describe("EnableAndDisableServiceType", func() { 195 | It("Enable And Disable Service Type", func() { 196 | serviceType := "SWARM" 197 | serviceImageId := "testImageId" 198 | serviceConfigSpec := &ServiceConfigurationSpec{ 199 | Type: serviceType, 200 | ImageID: serviceImageId, 201 | } 202 | 203 | mockTask := createMockTask("CONFIGURE_SERVICE", "COMPLETED") 204 | server.SetResponseJson(200, mockTask) 205 | enableTask, err := client.System.EnableServiceType(serviceConfigSpec) 206 | enableTask, err = client.Tasks.Wait(enableTask.ID) 207 | 208 | GinkgoT().Log(err) 209 | Expect(err).Should(BeNil()) 210 | Expect(enableTask).ShouldNot(BeNil()) 211 | Expect(enableTask.Operation).Should(Equal("CONFIGURE_SERVICE")) 212 | Expect(enableTask.State).Should(Equal("COMPLETED")) 213 | 214 | mockTask = createMockTask("DELETE_SERVICE_CONFIGURATION", "COMPLETED") 215 | server.SetResponseJson(200, mockTask) 216 | disableTask, err := client.System.DisableServiceType(serviceConfigSpec) 217 | disableTask, err = client.Tasks.Wait(disableTask.ID) 218 | 219 | GinkgoT().Log(err) 220 | Expect(err).Should(BeNil()) 221 | Expect(disableTask).ShouldNot(BeNil()) 222 | Expect(disableTask.Operation).Should(Equal("DELETE_SERVICE_CONFIGURATION")) 223 | Expect(disableTask.State).Should(Equal("COMPLETED")) 224 | }) 225 | }) 226 | 227 | Describe("ConfigureNsx", func() { 228 | It("Configure NSX", func() { 229 | nsxAddress := "nsxAddress" 230 | nsxUsername := "nsxUsername" 231 | nsxPassword := "nsxPassword" 232 | 233 | nsxConfigSpec := &NsxConfigurationSpec{ 234 | NsxAddress: nsxAddress, 235 | NsxUsername: nsxUsername, 236 | NsxPassword: nsxPassword, 237 | } 238 | 239 | mockTask := createMockTask("CONFIGURE_NSX", "COMPLETED") 240 | server.SetResponseJson(200, mockTask) 241 | enableTask, err := client.System.ConfigureNsx(nsxConfigSpec) 242 | enableTask, err = client.Tasks.Wait(enableTask.ID) 243 | 244 | GinkgoT().Log(err) 245 | Expect(err).Should(BeNil()) 246 | Expect(enableTask).ShouldNot(BeNil()) 247 | Expect(enableTask.Operation).Should(Equal("CONFIGURE_NSX")) 248 | Expect(enableTask.State).Should(Equal("COMPLETED")) 249 | }) 250 | }) 251 | }) 252 | -------------------------------------------------------------------------------- /photon/services_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = Describe("Service", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | kubernetesServiceSpec *ServiceCreateSpec 23 | tenantID string 24 | projID string 25 | ) 26 | 27 | BeforeEach(func() { 28 | if isIntegrationTest() { 29 | Skip("Skipping service test on integration mode. Need to set extendedProperties to use real IPs and masks") 30 | } 31 | server, client = testSetup() 32 | tenantID = createTenant(server, client) 33 | projID = createProject(server, client, tenantID) 34 | kubernetesMap := map[string]string{"dns": "1.1.1.1", "gateway": "1.1.1.2", "netmask": "255.255.255.128", 35 | "master_ip": "1.1.1.3", "container_network": "1.2.0.0/16"} 36 | kubernetesServiceSpec = &ServiceCreateSpec{ 37 | Name: randomString(10, "go-sdk-service-"), 38 | Type: "KUBERNETES", 39 | WorkerCount: 2, 40 | BatchSizeWorker: 1, 41 | ExtendedProperties: kubernetesMap, 42 | } 43 | }) 44 | 45 | AfterEach(func() { 46 | cleanServices(client, projID) 47 | cleanTenants(client) 48 | server.Close() 49 | }) 50 | 51 | Describe("CreateDeleteService", func() { 52 | It("Kubernetes service create and delete succeeds", func() { 53 | mockTask := createMockTask("CREATE_SERVICE", "COMPLETED") 54 | server.SetResponseJson(200, mockTask) 55 | 56 | task, err := client.Projects.CreateService(projID, kubernetesServiceSpec) 57 | task, err = client.Tasks.Wait(task.ID) 58 | GinkgoT().Log(err) 59 | Expect(err).Should(BeNil()) 60 | Expect(task).ShouldNot(BeNil()) 61 | Expect(task.Operation).Should(Equal("CREATE_SERVICE")) 62 | Expect(task.State).Should(Equal("COMPLETED")) 63 | 64 | mockTask = createMockTask("DELETE_SERVICE", "COMPLETED") 65 | server.SetResponseJson(200, mockTask) 66 | task, err = client.Services.Delete(task.Entity.ID) 67 | task, err = client.Tasks.Wait(task.ID) 68 | GinkgoT().Log(err) 69 | Expect(err).Should(BeNil()) 70 | Expect(task).ShouldNot(BeNil()) 71 | Expect(task.Operation).Should(Equal("DELETE_SERVICE")) 72 | Expect(task.State).Should(Equal("COMPLETED")) 73 | }) 74 | }) 75 | 76 | Describe("GetService", func() { 77 | It("Get service succeeds", func() { 78 | mockTask := createMockTask("CREATE_SERVICE", "COMPLETED") 79 | server.SetResponseJson(200, mockTask) 80 | 81 | task, err := client.Projects.CreateService(projID, kubernetesServiceSpec) 82 | task, err = client.Tasks.Wait(task.ID) 83 | GinkgoT().Log(err) 84 | Expect(err).Should(BeNil()) 85 | 86 | server.SetResponseJson(200, Service{Name: kubernetesServiceSpec.Name}) 87 | service, err := client.Services.Get(task.Entity.ID) 88 | GinkgoT().Log(err) 89 | Expect(err).Should(BeNil()) 90 | Expect(service).ShouldNot(BeNil()) 91 | Expect(service.Name).Should(Equal(kubernetesServiceSpec.Name)) 92 | 93 | mockTask = createMockTask("DELETE_SERVICE", "COMPLETED") 94 | server.SetResponseJson(200, mockTask) 95 | task, err = client.Services.Delete(task.Entity.ID) 96 | task, err = client.Tasks.Wait(task.ID) 97 | GinkgoT().Log(err) 98 | Expect(err).Should(BeNil()) 99 | }) 100 | }) 101 | 102 | Describe("GetVMs", func() { 103 | It("Get vms succeeds", func() { 104 | serviceVMName := "MasterVM" 105 | mockTask := createMockTask("CREATE_SERVICE", "COMPLETED") 106 | server.SetResponseJson(200, mockTask) 107 | 108 | task, err := client.Projects.CreateService(projID, kubernetesServiceSpec) 109 | task, err = client.Tasks.Wait(task.ID) 110 | GinkgoT().Log(err) 111 | Expect(err).Should(BeNil()) 112 | 113 | mockVm := VM{Name: serviceVMName} 114 | mockVmsPage := createMockVmsPage(mockVm) 115 | server.SetResponseJson(200, mockVmsPage) 116 | vmList, err := client.Services.GetVMs(projID) 117 | GinkgoT().Log(err) 118 | Expect(err).Should(BeNil()) 119 | Expect(vmList).ShouldNot(BeNil()) 120 | 121 | var found bool 122 | for _, vm := range vmList.Items { 123 | if vm.Name == serviceVMName { 124 | found = true 125 | break 126 | } 127 | } 128 | Expect(found).Should(BeTrue()) 129 | 130 | mockTask = createMockTask("DELETE_SERVICE", "COMPLETED") 131 | server.SetResponseJson(200, mockTask) 132 | task, err = client.Services.Delete(task.Entity.ID) 133 | task, err = client.Tasks.Wait(task.ID) 134 | GinkgoT().Log(err) 135 | Expect(err).Should(BeNil()) 136 | }) 137 | }) 138 | 139 | Describe("Resize service", func() { 140 | It("Resize succeeds", func() { 141 | mockTask := createMockTask("CREATE_SERVICE", "COMPLETED") 142 | server.SetResponseJson(200, mockTask) 143 | task, err := client.Projects.CreateService(projID, kubernetesServiceSpec) 144 | task, err = client.Tasks.Wait(task.ID) 145 | GinkgoT().Log(err) 146 | Expect(err).Should(BeNil()) 147 | 148 | serviceResize := &ServiceResizeOperation{NewWorkerCount: 3} 149 | mockTask = createMockTask("RESIZE_SERVICE", "COMPLETED") 150 | server.SetResponseJson(200, mockTask) 151 | task, err = client.Services.Resize(task.Entity.ID, serviceResize) 152 | GinkgoT().Log(err) 153 | Expect(err).Should(BeNil()) 154 | Expect(task).ShouldNot(BeNil()) 155 | Expect(task.Operation).Should(Equal("RESIZE_SERVICE")) 156 | Expect(task.State).Should(Equal("COMPLETED")) 157 | 158 | mockService := &Service{Name: kubernetesServiceSpec.Name, WorkerCount: serviceResize.NewWorkerCount} 159 | server.SetResponseJson(200, mockService) 160 | service, err := client.Services.Get(task.Entity.ID) 161 | GinkgoT().Log(err) 162 | Expect(err).Should(BeNil()) 163 | Expect(service).ShouldNot(BeNil()) 164 | Expect(service.Name).Should(Equal(kubernetesServiceSpec.Name)) 165 | Expect(service.WorkerCount).Should(Equal(serviceResize.NewWorkerCount)) 166 | 167 | mockTask = createMockTask("DELETE_SERVICE", "COMPLETED") 168 | server.SetResponseJson(200, mockTask) 169 | task, err = client.Services.Delete(task.Entity.ID) 170 | task, err = client.Tasks.Wait(task.ID) 171 | GinkgoT().Log(err) 172 | Expect(err).Should(BeNil()) 173 | }) 174 | }) 175 | 176 | Describe("Trigger service maintenance", func() { 177 | It("Trigger service maintenance succeeds", func() { 178 | mockTask := createMockTask("CREATE_SERVICE", "COMPLETED") 179 | server.SetResponseJson(200, mockTask) 180 | task, err := client.Projects.CreateService(projID, kubernetesServiceSpec) 181 | task, err = client.Tasks.Wait(task.ID) 182 | GinkgoT().Log(err) 183 | Expect(err).Should(BeNil()) 184 | 185 | mockTask = createMockTask("TRIGGER_SERVICE_MAINTENANCE", "COMPLETED") 186 | server.SetResponseJson(200, mockTask) 187 | task, err = client.Services.TriggerMaintenance(task.Entity.ID) 188 | GinkgoT().Log(err) 189 | Expect(err).Should(BeNil()) 190 | Expect(task).ShouldNot(BeNil()) 191 | Expect(task.Operation).Should(Equal("TRIGGER_SERVICE_MAINTENANCE")) 192 | Expect(task.State).Should(Equal("COMPLETED")) 193 | 194 | mockTask = createMockTask("DELETE_SERVICE", "COMPLETED") 195 | server.SetResponseJson(200, mockTask) 196 | task, err = client.Services.Delete(task.Entity.ID) 197 | task, err = client.Tasks.Wait(task.ID) 198 | GinkgoT().Log(err) 199 | Expect(err).Should(BeNil()) 200 | }) 201 | }) 202 | 203 | Describe("Change version service", func() { 204 | It("Change version succeeds", func() { 205 | imageID := createImage(server, client) 206 | mockTask := createMockTask("CREATE_SERVICE", "COMPLETED") 207 | server.SetResponseJson(200, mockTask) 208 | task, err := client.Projects.CreateService(projID, kubernetesServiceSpec) 209 | task, err = client.Tasks.Wait(task.ID) 210 | GinkgoT().Log(err) 211 | Expect(err).Should(BeNil()) 212 | 213 | changeVersion := &ServiceChangeVersionOperation{NewImageID: imageID} 214 | mockTask = createMockTask("CHANGE_VERSION_SERVICE", "COMPLETED") 215 | server.SetResponseJson(200, mockTask) 216 | task, err = client.Services.ChangeVersion(task.Entity.ID, changeVersion) 217 | GinkgoT().Log(err) 218 | Expect(err).Should(BeNil()) 219 | Expect(task).ShouldNot(BeNil()) 220 | Expect(task.Operation).Should(Equal("CHANGE_VERSION_SERVICE")) 221 | Expect(task.State).Should(Equal("COMPLETED")) 222 | 223 | mockTask = createMockTask("DELETE_SERVICE", "COMPLETED") 224 | server.SetResponseJson(200, mockTask) 225 | task, err = client.Services.Delete(task.Entity.ID) 226 | task, err = client.Tasks.Wait(task.ID) 227 | GinkgoT().Log(err) 228 | Expect(err).Should(BeNil()) 229 | }) 230 | }) 231 | }) 232 | -------------------------------------------------------------------------------- /photon/tenants.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | "bytes" 14 | "encoding/json" 15 | "fmt" 16 | "io/ioutil" 17 | ) 18 | 19 | // Contains functionality for tenants API. 20 | type TenantsAPI struct { 21 | client *Client 22 | } 23 | 24 | // Options for GetResourceTickets API. 25 | type ResourceTicketGetOptions struct { 26 | Name string `urlParam:"name"` 27 | } 28 | 29 | // Options for GetProjects API. 30 | type ProjectGetOptions struct { 31 | Name string `urlParam:"name"` 32 | } 33 | 34 | var tenantUrl string = rootUrl + "/tenants" 35 | 36 | // Returns all tenants on an photon instance. 37 | func (api *TenantsAPI) GetAll() (result *Tenants, err error) { 38 | uri := api.client.Endpoint + tenantUrl 39 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 40 | if err != nil { 41 | return 42 | } 43 | 44 | result = &Tenants{} 45 | err = json.Unmarshal(res, result) 46 | return 47 | } 48 | 49 | // Creates a tenant. 50 | func (api *TenantsAPI) Create(tenantSpec *TenantCreateSpec) (task *Task, err error) { 51 | body, err := json.Marshal(tenantSpec) 52 | if err != nil { 53 | return 54 | } 55 | res, err := api.client.restClient.Post( 56 | api.client.Endpoint+tenantUrl, 57 | "application/json", 58 | bytes.NewReader(body), 59 | api.client.options.TokenOptions) 60 | if err != nil { 61 | return 62 | } 63 | defer res.Body.Close() 64 | task, err = getTask(getError(res)) 65 | return 66 | } 67 | 68 | // Deletes the tenant with specified ID. Any projects, VMs, disks, etc., owned by the tenant must be deleted first. 69 | func (api *TenantsAPI) Delete(id string) (task *Task, err error) { 70 | res, err := api.client.restClient.Delete(api.client.Endpoint+tenantUrl+"/"+id, api.client.options.TokenOptions) 71 | if err != nil { 72 | return 73 | } 74 | defer res.Body.Close() 75 | task, err = getTask(getError(res)) 76 | return 77 | } 78 | 79 | // Creates a project on the specified tenant. 80 | func (api *TenantsAPI) CreateProject(tenantId string, spec *ProjectCreateSpec) (task *Task, err error) { 81 | body, err := json.Marshal(spec) 82 | if err != nil { 83 | return 84 | } 85 | res, err := api.client.restClient.Post( 86 | api.client.Endpoint+tenantUrl+"/"+tenantId+"/projects", 87 | "application/json", 88 | bytes.NewReader(body), 89 | api.client.options.TokenOptions) 90 | if err != nil { 91 | return 92 | } 93 | defer res.Body.Close() 94 | task, err = getTask(getError(res)) 95 | return 96 | } 97 | 98 | // Gets the projects for tenant with the specified ID, using options to filter the results. 99 | // If options is nil, no filtering will occur. 100 | func (api *TenantsAPI) GetProjects(tenantId string, options *ProjectGetOptions) (result *ProjectList, err error) { 101 | uri := api.client.Endpoint + tenantUrl + "/" + tenantId + "/projects" 102 | if options != nil { 103 | uri += getQueryString(options) 104 | } 105 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 106 | if err != nil { 107 | return 108 | } 109 | 110 | result = &(ProjectList{}) 111 | err = json.Unmarshal(res, result) 112 | return 113 | } 114 | 115 | // Gets all tasks with the specified tenant ID, using options to filter the results. 116 | // If options is nil, no filtering will occur. 117 | func (api *TenantsAPI) GetTasks(id string, options *TaskGetOptions) (result *TaskList, err error) { 118 | uri := api.client.Endpoint + tenantUrl + "/" + id + "/tasks" 119 | if options != nil { 120 | uri += getQueryString(options) 121 | } 122 | res, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 123 | if err != nil { 124 | return 125 | } 126 | 127 | result = &TaskList{} 128 | err = json.Unmarshal(res, result) 129 | return 130 | } 131 | 132 | // Gets a tenant with the specified ID or name 133 | func (api *TenantsAPI) Get(identity string) (tenant *Tenant, err error) { 134 | res, err := api.client.restClient.Get(api.getEntityUrl(identity), api.client.options.TokenOptions) 135 | if err != nil { 136 | return 137 | } 138 | defer res.Body.Close() 139 | res, err = getError(res) 140 | if err != nil { 141 | return 142 | } 143 | tenant = &Tenant{} 144 | if res != nil { 145 | err = json.NewDecoder(res.Body).Decode(tenant) 146 | // ID corresponds to the tenant ID found, return tenant 147 | if err == nil { 148 | return 149 | } 150 | } 151 | // Find by Name 152 | uri := api.client.Endpoint + tenantUrl + "?name=" + identity 153 | res2, err := api.client.restClient.GetList(api.client.Endpoint, uri, api.client.options.TokenOptions) 154 | 155 | if err != nil { 156 | return 157 | } 158 | 159 | tenants := &Tenants{} 160 | err = json.Unmarshal(res2, tenants) 161 | if err != nil { 162 | return 163 | } 164 | 165 | if len(tenants.Items) < 1 { 166 | err = fmt.Errorf("Cannot find a tenant with id or name match %s", identity) 167 | return 168 | } 169 | 170 | tenant = &(tenants.Items[0]) 171 | return 172 | } 173 | 174 | // Set security groups for this tenant, overwriting any existing ones. 175 | func (api *TenantsAPI) SetSecurityGroups(id string, securityGroups *SecurityGroupsSpec) (*Task, error) { 176 | return setSecurityGroups(api.client, api.getEntityUrl(id), securityGroups) 177 | } 178 | 179 | func (api *TenantsAPI) getEntityUrl(id string) (url string) { 180 | return api.client.Endpoint + tenantUrl + "/" + id 181 | } 182 | 183 | // Get quota for project with the specified ID. 184 | func (api *TenantsAPI) GetQuota(tenantId string) (quota *Quota, err error) { 185 | uri := api.client.Endpoint + tenantUrl + "/" + tenantId + "/quota" 186 | res, err := api.client.restClient.Get(uri, api.client.options.TokenOptions) 187 | 188 | if err != nil { 189 | return 190 | } 191 | 192 | defer res.Body.Close() 193 | res, err = getError(res) 194 | if err != nil { 195 | return 196 | } 197 | 198 | body, err := ioutil.ReadAll(res.Body) 199 | if err != nil { 200 | return 201 | } 202 | 203 | quota = &Quota{} 204 | err = json.Unmarshal(body, quota) 205 | return 206 | } 207 | 208 | // Set (replace) the whole project quota with the quota line items specified in quota spec. 209 | func (api *TenantsAPI) SetQuota(tenantId string, spec *QuotaSpec) (task *Task, err error) { 210 | task, err = api.modifyQuota("PUT", tenantId, spec) 211 | return 212 | } 213 | 214 | // Update portion of the project quota with the quota line items specified in quota spec. 215 | func (api *TenantsAPI) UpdateQuota(tenantId string, spec *QuotaSpec) (task *Task, err error) { 216 | task, err = api.modifyQuota("PATCH", tenantId, spec) 217 | return 218 | } 219 | 220 | // Exclude project quota line items from the specific quota spec. 221 | func (api *TenantsAPI) ExcludeQuota(tenantId string, spec *QuotaSpec) (task *Task, err error) { 222 | task, err = api.modifyQuota("DELETE", tenantId, spec) 223 | return 224 | } 225 | 226 | // A private common function for modifying quota for the specified project with the quota line items specified 227 | // in quota spec. 228 | func (api *TenantsAPI) modifyQuota(method string, tenantId string, spec *QuotaSpec) (task *Task, err error) { 229 | body, err := json.Marshal(spec) 230 | if err != nil { 231 | return 232 | } 233 | res, err := api.client.restClient.SendRequestCommon( 234 | method, 235 | api.client.Endpoint+tenantUrl+"/"+tenantId+"/quota", 236 | "application/json", 237 | bytes.NewReader(body), 238 | api.client.options.TokenOptions) 239 | if err != nil { 240 | return 241 | } 242 | defer res.Body.Close() 243 | task, err = getTask(getError(res)) 244 | return 245 | } 246 | 247 | // Gets IAM Policy of a tenant. 248 | func (api *TenantsAPI) GetIam(tenantId string) (policy []*RoleBinding, err error) { 249 | res, err := api.client.restClient.Get( 250 | api.client.Endpoint+tenantUrl+"/"+tenantId+"/iam", 251 | api.client.options.TokenOptions) 252 | if err != nil { 253 | return 254 | } 255 | defer res.Body.Close() 256 | res, err = getError(res) 257 | if err != nil { 258 | return 259 | } 260 | err = json.NewDecoder(res.Body).Decode(&policy) 261 | return policy, err 262 | } 263 | 264 | // Sets IAM Policy on a tenant. 265 | func (api *TenantsAPI) SetIam(tenantId string, policy []*RoleBinding) (task *Task, err error) { 266 | body, err := json.Marshal(policy) 267 | if err != nil { 268 | return 269 | } 270 | res, err := api.client.restClient.Post( 271 | api.client.Endpoint+tenantUrl+"/"+tenantId+"/iam", 272 | "application/json", 273 | bytes.NewReader(body), 274 | api.client.options.TokenOptions) 275 | if err != nil { 276 | return 277 | } 278 | defer res.Body.Close() 279 | task, err = getTask(getError(res)) 280 | return 281 | } 282 | 283 | // Modifies IAM Policy on a tenant. 284 | func (api *TenantsAPI) ModifyIam(tenantId string, policyDelta []*RoleBindingDelta) (task *Task, err error) { 285 | body, err := json.Marshal(policyDelta) 286 | if err != nil { 287 | return 288 | } 289 | res, err := api.client.restClient.Patch( 290 | api.client.Endpoint+tenantUrl+"/"+tenantId+"/iam", 291 | "application/json", 292 | bytes.NewReader(body), 293 | api.client.options.TokenOptions) 294 | if err != nil { 295 | return 296 | } 297 | defer res.Body.Close() 298 | task, err = getTask(getError(res)) 299 | return 300 | } 301 | -------------------------------------------------------------------------------- /photon/disks_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = Describe("Disk", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | tenantID string 23 | projID string 24 | flavorName string 25 | flavorID string 26 | diskSpec *DiskCreateSpec 27 | ) 28 | 29 | BeforeEach(func() { 30 | server, client = testSetup() 31 | tenantID = createTenant(server, client) 32 | projID = createProject(server, client, tenantID) 33 | flavorName, flavorID = createFlavor(server, client) 34 | diskSpec = &DiskCreateSpec{ 35 | Flavor: flavorName, 36 | Kind: "persistent-disk", 37 | CapacityGB: 2, 38 | Name: randomString(10, "go-sdk-disk-"), 39 | } 40 | }) 41 | 42 | AfterEach(func() { 43 | cleanDisks(client, projID) 44 | cleanFlavors(client) 45 | cleanTenants(client) 46 | server.Close() 47 | }) 48 | 49 | Describe("CreateAndDeleteDisk", func() { 50 | It("Disk create and delete succeeds", func() { 51 | mockTask := createMockTask("CREATE_DISK", "COMPLETED") 52 | server.SetResponseJson(200, mockTask) 53 | 54 | task, err := client.Projects.CreateDisk(projID, diskSpec) 55 | task, err = client.Tasks.Wait(task.ID) 56 | GinkgoT().Log(err) 57 | Expect(err).Should(BeNil()) 58 | Expect(task).ShouldNot(BeNil()) 59 | Expect(task.Operation).Should(Equal("CREATE_DISK")) 60 | Expect(task.State).Should(Equal("COMPLETED")) 61 | 62 | mockTask = createMockTask("DELETE_DISK", "COMPLETED") 63 | server.SetResponseJson(200, mockTask) 64 | task, err = client.Disks.Delete(task.Entity.ID) 65 | task, err = client.Tasks.Wait(task.ID) 66 | 67 | GinkgoT().Log(err) 68 | Expect(err).Should(BeNil()) 69 | Expect(task).ShouldNot(BeNil()) 70 | Expect(task.Operation).Should(Equal("DELETE_DISK")) 71 | Expect(task.State).Should(Equal("COMPLETED")) 72 | }) 73 | }) 74 | 75 | Describe("GetDisk", func() { 76 | It("Get disk returns a disk ID", func() { 77 | mockTask := createMockTask("CREATE_DISK", "COMPLETED") 78 | server.SetResponseJson(200, mockTask) 79 | 80 | task, err := client.Projects.CreateDisk(projID, diskSpec) 81 | task, err = client.Tasks.Wait(task.ID) 82 | GinkgoT().Log(err) 83 | Expect(err).Should(BeNil()) 84 | 85 | diskMock := &PersistentDisk{ 86 | Name: diskSpec.Name, 87 | Flavor: diskSpec.Flavor, 88 | CapacityGB: diskSpec.CapacityGB, 89 | Kind: diskSpec.Kind, 90 | } 91 | server.SetResponseJson(200, diskMock) 92 | disk, err := client.Disks.Get(task.Entity.ID) 93 | 94 | GinkgoT().Log(err) 95 | Expect(err).Should(BeNil()) 96 | Expect(disk.Name).Should(Equal(diskSpec.Name)) 97 | Expect(disk.Flavor).Should(Equal(diskSpec.Flavor)) 98 | Expect(disk.Kind).Should(Equal(diskSpec.Kind)) 99 | Expect(disk.CapacityGB).Should(Equal(diskSpec.CapacityGB)) 100 | Expect(disk.ID).Should(Equal(task.Entity.ID)) 101 | 102 | mockTask = createMockTask("DELETE_DISK", "COMPLETED") 103 | server.SetResponseJson(200, mockTask) 104 | task, err = client.Disks.Delete(task.Entity.ID) 105 | task, err = client.Tasks.Wait(task.ID) 106 | GinkgoT().Log(err) 107 | Expect(err).Should(BeNil()) 108 | }) 109 | }) 110 | 111 | Describe("GetTasks", func() { 112 | It("GetTasks returns a completed task", func() { 113 | mockTask := createMockTask("CREATE_DISK", "COMPLETED") 114 | server.SetResponseJson(200, mockTask) 115 | 116 | task, err := client.Projects.CreateDisk(projID, diskSpec) 117 | task, err = client.Tasks.Wait(task.ID) 118 | GinkgoT().Log(err) 119 | Expect(err).Should(BeNil()) 120 | 121 | mockTasksPage := createMockTasksPage(*mockTask) 122 | server.SetResponseJson(200, mockTasksPage) 123 | taskList, err := client.Disks.GetTasks(task.Entity.ID, &TaskGetOptions{}) 124 | 125 | GinkgoT().Log(err) 126 | Expect(err).Should(BeNil()) 127 | Expect(taskList).ShouldNot(BeNil()) 128 | Expect(taskList.Items).Should(ContainElement(*task)) 129 | 130 | mockTask = createMockTask("DELETE_DISK", "COMPLETED") 131 | server.SetResponseJson(200, mockTask) 132 | task, err = client.Disks.Delete(task.Entity.ID) 133 | task, err = client.Tasks.Wait(task.ID) 134 | GinkgoT().Log(err) 135 | Expect(err).Should(BeNil()) 136 | }) 137 | }) 138 | 139 | Describe("ManageDiskIamPolicy", func() { 140 | It("Set IAM Policy succeeds", func() { 141 | mockTask := createMockTask("CREATE_DISK", "COMPLETED") 142 | server.SetResponseJson(200, mockTask) 143 | 144 | task, err := client.Projects.CreateDisk(projID, diskSpec) 145 | task, err = client.Tasks.Wait(task.ID) 146 | GinkgoT().Log(err) 147 | Expect(err).Should(BeNil()) 148 | Expect(task).ShouldNot(BeNil()) 149 | Expect(task.Operation).Should(Equal("CREATE_DISK")) 150 | Expect(task.State).Should(Equal("COMPLETED")) 151 | 152 | diskID := task.Entity.ID 153 | mockTask = createMockTask("SET_IAM_POLICY", "COMPLETED") 154 | server.SetResponseJson(200, mockTask) 155 | var policy []*RoleBinding 156 | policy = []*RoleBinding{{Role: "owner", Subjects: []string{"joe@photon.local"}}} 157 | task, err = client.Disks.SetIam(diskID, policy) 158 | task, err = client.Tasks.Wait(task.ID) 159 | GinkgoT().Log(err) 160 | Expect(err).Should(BeNil()) 161 | Expect(task).ShouldNot(BeNil()) 162 | Expect(task.Operation).Should(Equal("SET_IAM_POLICY")) 163 | Expect(task.State).Should(Equal("COMPLETED")) 164 | 165 | mockTask = createMockTask("DELETE_DISK", "COMPLETED") 166 | server.SetResponseJson(200, mockTask) 167 | task, err = client.Disks.Delete(diskID) 168 | task, err = client.Tasks.Wait(task.ID) 169 | 170 | GinkgoT().Log(err) 171 | Expect(err).Should(BeNil()) 172 | Expect(task).ShouldNot(BeNil()) 173 | Expect(task.Operation).Should(Equal("DELETE_DISK")) 174 | Expect(task.State).Should(Equal("COMPLETED")) 175 | }) 176 | 177 | It("Modify IAM Policy succeeds", func() { 178 | mockTask := createMockTask("CREATE_DISK", "COMPLETED") 179 | server.SetResponseJson(200, mockTask) 180 | 181 | task, err := client.Projects.CreateDisk(projID, diskSpec) 182 | task, err = client.Tasks.Wait(task.ID) 183 | GinkgoT().Log(err) 184 | Expect(err).Should(BeNil()) 185 | Expect(task).ShouldNot(BeNil()) 186 | Expect(task.Operation).Should(Equal("CREATE_DISK")) 187 | Expect(task.State).Should(Equal("COMPLETED")) 188 | 189 | diskID := task.Entity.ID 190 | mockTask = createMockTask("MODIFY_IAM_POLICY", "COMPLETED") 191 | server.SetResponseJson(200, mockTask) 192 | var delta []*RoleBindingDelta 193 | delta = []*RoleBindingDelta{{Subject: "joe@photon.local", Action: "ADD", Role: "owner"}} 194 | task, err = client.Disks.ModifyIam(diskID, delta) 195 | task, err = client.Tasks.Wait(task.ID) 196 | GinkgoT().Log(err) 197 | Expect(err).Should(BeNil()) 198 | Expect(task).ShouldNot(BeNil()) 199 | Expect(task.Operation).Should(Equal("MODIFY_IAM_POLICY")) 200 | Expect(task.State).Should(Equal("COMPLETED")) 201 | 202 | mockTask = createMockTask("DELETE_DISK", "COMPLETED") 203 | server.SetResponseJson(200, mockTask) 204 | task, err = client.Disks.Delete(diskID) 205 | task, err = client.Tasks.Wait(task.ID) 206 | 207 | GinkgoT().Log(err) 208 | Expect(err).Should(BeNil()) 209 | Expect(task).ShouldNot(BeNil()) 210 | Expect(task.Operation).Should(Equal("DELETE_DISK")) 211 | Expect(task.State).Should(Equal("COMPLETED")) 212 | }) 213 | 214 | It("Get IAM Policy succeeds", func() { 215 | mockTask := createMockTask("CREATE_DISK", "COMPLETED") 216 | server.SetResponseJson(200, mockTask) 217 | 218 | task, err := client.Projects.CreateDisk(projID, diskSpec) 219 | task, err = client.Tasks.Wait(task.ID) 220 | GinkgoT().Log(err) 221 | Expect(err).Should(BeNil()) 222 | Expect(task).ShouldNot(BeNil()) 223 | Expect(task.Operation).Should(Equal("CREATE_DISK")) 224 | Expect(task.State).Should(Equal("COMPLETED")) 225 | 226 | diskID := task.Entity.ID 227 | mockTask = createMockTask("SET_IAM_POLICY", "COMPLETED") 228 | server.SetResponseJson(200, mockTask) 229 | var policy []*RoleBinding 230 | policy = []*RoleBinding{{Role: "owner", Subjects: []string{"joe@photon.local"}}} 231 | task, err = client.Disks.SetIam(diskID, policy) 232 | task, err = client.Tasks.Wait(task.ID) 233 | GinkgoT().Log(err) 234 | Expect(err).Should(BeNil()) 235 | Expect(task).ShouldNot(BeNil()) 236 | Expect(task.Operation).Should(Equal("SET_IAM_POLICY")) 237 | Expect(task.State).Should(Equal("COMPLETED")) 238 | 239 | server.SetResponseJson(200, policy) 240 | response, err := client.Disks.GetIam(diskID) 241 | GinkgoT().Log(err) 242 | Expect(err).Should(BeNil()) 243 | Expect(response[0].Subjects).Should(Equal(policy[0].Subjects)) 244 | Expect(response[0].Role).Should(Equal(policy[0].Role)) 245 | 246 | mockTask = createMockTask("DELETE_DISK", "COMPLETED") 247 | server.SetResponseJson(200, mockTask) 248 | task, err = client.Disks.Delete(diskID) 249 | task, err = client.Tasks.Wait(task.ID) 250 | 251 | GinkgoT().Log(err) 252 | Expect(err).Should(BeNil()) 253 | Expect(task).ShouldNot(BeNil()) 254 | Expect(task.Operation).Should(Equal("DELETE_DISK")) 255 | Expect(task.State).Should(Equal("COMPLETED")) 256 | }) 257 | }) 258 | }) 259 | -------------------------------------------------------------------------------- /photon/hosts_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 VMware, Inc. All Rights Reserved. 2 | // 3 | // This product is licensed to you under the Apache License, Version 2.0 (the "License"). 4 | // You may not use this product except in compliance with the License. 5 | // 6 | // This product may include a number of subcomponents with separate copyright notices and 7 | // license terms. Your use of these subcomponents is subject to the terms and conditions 8 | // of the subcomponent's license, as noted in the LICENSE file. 9 | 10 | package photon 11 | 12 | import ( 13 | . "github.com/onsi/ginkgo" 14 | . "github.com/onsi/gomega" 15 | "github.com/vmware/photon-controller-go-sdk/photon/internal/mocks" 16 | ) 17 | 18 | var _ = Describe("Host", func() { 19 | var ( 20 | server *mocks.Server 21 | client *Client 22 | hostSpec *HostCreateSpec 23 | ) 24 | 25 | BeforeEach(func() { 26 | if isIntegrationTest() { 27 | Skip("Skipping Host test on integration mode. Unable to prevent address host collision") 28 | } 29 | server, client = testSetup() 30 | hostSpec = &HostCreateSpec{ 31 | Username: randomString(10), 32 | Password: randomString(10), 33 | Address: randomAddress(), 34 | Tags: []string{"CLOUD"}, 35 | Metadata: map[string]string{"Test": "go-sdk-host"}, 36 | } 37 | }) 38 | 39 | AfterEach(func() { 40 | cleanHosts(client) 41 | server.Close() 42 | }) 43 | 44 | Describe("ProvisionHost", func() { 45 | It("host provisioning succeeds", func() { 46 | mockTask := createMockTask("CREATE_HOST", "COMPLETED") 47 | server.SetResponseJson(200, mockTask) 48 | 49 | task, err := client.InfraHosts.Create(hostSpec) 50 | task, err = client.Tasks.Wait(task.ID) 51 | GinkgoT().Log(err) 52 | Expect(err).Should(BeNil()) 53 | 54 | mockTask = createMockTask("PROVISION_HOST", "COMPLETED") 55 | server.SetResponseJson(202, mockTask) 56 | 57 | task, err = client.Hosts.Provision(task.Entity.ID) 58 | task, err = client.Tasks.Wait(task.ID) 59 | 60 | GinkgoT().Log(err) 61 | Expect(err).Should(BeNil()) 62 | Expect(task).ShouldNot(BeNil()) 63 | Expect(task.Operation).Should(Equal("PROVISION_HOST")) 64 | Expect(task.State).Should(Equal("COMPLETED")) 65 | 66 | mockTask = createMockTask("DELETE_HOST", "COMPLETED") 67 | server.SetResponseJson(200, mockTask) 68 | task, err = client.InfraHosts.Delete(task.Entity.ID) 69 | task, err = client.Tasks.Wait(task.ID) 70 | GinkgoT().Log(err) 71 | Expect(err).Should(BeNil()) 72 | 73 | }) 74 | }) 75 | 76 | Describe("GetHosts", func() { 77 | It("GetHosts succeeds", func() { 78 | mockTask := createMockTask("CREATE_HOST", "COMPLETED") 79 | server.SetResponseJson(200, mockTask) 80 | 81 | task, err := client.InfraHosts.Create(hostSpec) 82 | task, err = client.Tasks.Wait(task.ID) 83 | GinkgoT().Log(err) 84 | Expect(err).Should(BeNil()) 85 | 86 | server.SetResponseJson(200, Host{Tags: hostSpec.Tags, ID: task.Entity.ID}) 87 | host, err := client.InfraHosts.Get(task.Entity.ID) 88 | GinkgoT().Log(err) 89 | Expect(err).Should(BeNil()) 90 | Expect(host.ID).Should(Equal(task.Entity.ID)) 91 | Expect(host.Tags).Should(Equal(hostSpec.Tags)) 92 | 93 | mockTask = createMockTask("DELETE_HOST", "COMPLETED") 94 | server.SetResponseJson(200, mockTask) 95 | task, err = client.InfraHosts.Delete(task.Entity.ID) 96 | task, err = client.Tasks.Wait(task.ID) 97 | GinkgoT().Log(err) 98 | Expect(err).Should(BeNil()) 99 | }) 100 | 101 | It("GetAll returns a host", func() { 102 | mockTask := createMockTask("CREATE_HOST", "COMPLETED") 103 | server.SetResponseJson(200, mockTask) 104 | 105 | task, err := client.InfraHosts.Create(hostSpec) 106 | task, err = client.Tasks.Wait(task.ID) 107 | GinkgoT().Log(err) 108 | Expect(err).Should(BeNil()) 109 | 110 | server.SetResponseJson(200, &Hosts{[]Host{Host{Tags: hostSpec.Tags, ID: task.Entity.ID}}}) 111 | hostList, err := client.InfraHosts.GetHosts() 112 | GinkgoT().Log(err) 113 | Expect(err).Should(BeNil()) 114 | Expect(hostList).ShouldNot(BeNil()) 115 | 116 | var found bool 117 | for _, host := range hostList.Items { 118 | if host.ID == task.Entity.ID { 119 | found = true 120 | break 121 | } 122 | } 123 | Expect(found).Should(BeTrue()) 124 | 125 | mockTask = createMockTask("DELETE_HOST", "COMPLETED") 126 | server.SetResponseJson(200, mockTask) 127 | task, err = client.InfraHosts.Delete(task.Entity.ID) 128 | task, err = client.Tasks.Wait(task.ID) 129 | GinkgoT().Log(err) 130 | Expect(err).Should(BeNil()) 131 | }) 132 | }) 133 | 134 | Describe("SetHostAvailabilityZone", func() { 135 | It("set host's availability zone", func() { 136 | mockTask := createMockTask("SET_AVAILABILITYZONE", "COMPLETED") 137 | server.SetResponseJson(200, mockTask) 138 | 139 | hostSetAvailabilityZoneOperation := &HostSetAvailabilityZoneOperation{AvailabilityZoneId: "availability-zone-Id"} 140 | task, err := client.Hosts.SetAvailabilityZone("host-Id", hostSetAvailabilityZoneOperation) 141 | task, err = client.Tasks.Wait(task.ID) 142 | GinkgoT().Log(err) 143 | Expect(err).Should(BeNil()) 144 | Expect(task).ShouldNot(BeNil()) 145 | Expect(task.Operation).Should(Equal("SET_AVAILABILITYZONE")) 146 | Expect(task.State).Should(Equal("COMPLETED")) 147 | }) 148 | }) 149 | 150 | Describe("GetTasks", func() { 151 | It("GetTasks returns a completed task", func() { 152 | mockTask := createMockTask("CREATE_HOST", "COMPLETED") 153 | mockTask.Entity.ID = "mock-task-id" 154 | server.SetResponseJson(200, mockTask) 155 | 156 | task, err := client.InfraHosts.Create(hostSpec) 157 | task, err = client.Tasks.Wait(task.ID) 158 | GinkgoT().Log(err) 159 | Expect(err).Should(BeNil()) 160 | 161 | mockTasksPage := createMockTasksPage(*mockTask) 162 | server.SetResponseJson(200, mockTasksPage) 163 | taskList, err := client.Hosts.GetTasks(task.Entity.ID, &TaskGetOptions{}) 164 | 165 | GinkgoT().Log(err) 166 | Expect(err).Should(BeNil()) 167 | Expect(taskList).ShouldNot(BeNil()) 168 | Expect(taskList.Items).Should(ContainElement(*task)) 169 | 170 | mockTask = createMockTask("DELETE_HOST", "COMPLETED") 171 | server.SetResponseJson(200, mockTask) 172 | task, err = client.InfraHosts.Delete(task.Entity.ID) 173 | task, err = client.Tasks.Wait(task.ID) 174 | GinkgoT().Log(err) 175 | Expect(err).Should(BeNil()) 176 | }) 177 | }) 178 | 179 | Describe("SuspendHostAndResumeHost", func() { 180 | It("Suspend Host and Resume Host succeeds", func() { 181 | mockTask := createMockTask("CREATE_HOST", "COMPLETED") 182 | server.SetResponseJson(200, mockTask) 183 | 184 | task, err := client.InfraHosts.Create(hostSpec) 185 | task, err = client.Tasks.Wait(task.ID) 186 | GinkgoT().Log(err) 187 | Expect(err).Should(BeNil()) 188 | 189 | mockTask = createMockTask("SUSPEND_HOST", "COMPLETED") 190 | server.SetResponseJson(200, mockTask) 191 | 192 | task, err = client.InfraHosts.Suspend(task.Entity.ID) 193 | task, err = client.Tasks.Wait(task.ID) 194 | 195 | GinkgoT().Log(err) 196 | Expect(err).Should(BeNil()) 197 | Expect(task).ShouldNot(BeNil()) 198 | Expect(task.Operation).Should(Equal("SUSPEND_HOST")) 199 | Expect(task.State).Should(Equal("COMPLETED")) 200 | 201 | mockTask = createMockTask("RESUME_HOST", "COMPLETED") 202 | server.SetResponseJson(200, mockTask) 203 | 204 | task, err = client.InfraHosts.Resume(task.Entity.ID) 205 | task, err = client.Tasks.Wait(task.ID) 206 | 207 | GinkgoT().Log(err) 208 | Expect(err).Should(BeNil()) 209 | Expect(task).ShouldNot(BeNil()) 210 | Expect(task.Operation).Should(Equal("RESUME_HOST")) 211 | Expect(task.State).Should(Equal("COMPLETED")) 212 | }) 213 | }) 214 | 215 | Describe("EnterAndExitMaintenanceMode", func() { 216 | var ( 217 | hostID string 218 | ) 219 | 220 | BeforeEach(func() { 221 | hostID = "" 222 | 223 | // Create host 224 | mockTask := createMockTask("CREATE_HOST", "COMPLETED") 225 | server.SetResponseJson(200, mockTask) 226 | task, err := client.InfraHosts.Create(hostSpec) 227 | Expect(err).Should(BeNil()) 228 | 229 | task, err = client.Tasks.Wait(task.ID) 230 | if task != nil { 231 | hostID = task.Entity.ID 232 | } 233 | Expect(err).Should(BeNil()) 234 | 235 | // Suspend host 236 | mockTask = createMockTask("SUSPEND_HOST", "COMPLETED") 237 | server.SetResponseJson(200, mockTask) 238 | task, err = client.InfraHosts.Suspend(hostID) 239 | task, err = client.Tasks.Wait(task.ID) 240 | Expect(err).Should(BeNil()) 241 | }) 242 | 243 | AfterEach(func() { 244 | // Delete host 245 | if len(hostID) > 0 { 246 | mockTask := createMockTask("DELETE_HOST", "COMPLETED") 247 | server.SetResponseJson(200, mockTask) 248 | task, err := client.InfraHosts.Delete(hostID) 249 | task, err = client.Tasks.Wait(task.ID) 250 | if err != nil { 251 | GinkgoT().Log(err) 252 | } 253 | } 254 | }) 255 | 256 | It("Host Enter and Exit Maintenance Mode succeeds", func() { 257 | mockTask := createMockTask("ENTER_MAINTENANCE_MODE", "COMPLETED") 258 | server.SetResponseJson(200, mockTask) 259 | task, err := client.InfraHosts.EnterMaintenanceMode(hostID) 260 | task, err = client.Tasks.Wait(task.ID) 261 | 262 | GinkgoT().Log(err) 263 | Expect(err).Should(BeNil()) 264 | Expect(task).ShouldNot(BeNil()) 265 | Expect(task.Operation).Should(Equal("ENTER_MAINTENANCE_MODE")) 266 | Expect(task.State).Should(Equal("COMPLETED")) 267 | 268 | mockTask = createMockTask("EXIT_MAINTENANCE_MODE", "COMPLETED") 269 | server.SetResponseJson(200, mockTask) 270 | task, err = client.InfraHosts.ExitMaintenanceMode(hostID) 271 | task, err = client.Tasks.Wait(task.ID) 272 | 273 | GinkgoT().Log(err) 274 | Expect(err).Should(BeNil()) 275 | Expect(task).ShouldNot(BeNil()) 276 | Expect(task.Operation).Should(Equal("EXIT_MAINTENANCE_MODE")) 277 | Expect(task.State).Should(Equal("COMPLETED")) 278 | }) 279 | }) 280 | }) 281 | --------------------------------------------------------------------------------