Skip to content

Commit

Permalink
Add support for Cloud Quota Management API
Browse files Browse the repository at this point in the history
To work with "limits" and "quotas", we now need to handle
the Cloud Quota Management API instead of the Cloud Management API.

QuotaRegionalClient is a Proxy of the HTTP client. It resolves
the URL of the service in the requested region and adds X-Auth-Token to
the headers.

The functionality for issuing and caching the X-Auth-Token, as well as
resolving the service URL is encapsulated in the IdentityManager.
  • Loading branch information
Voloshina Tatyana committed Nov 17, 2022
1 parent 647897b commit 27e2062
Show file tree
Hide file tree
Showing 22 changed files with 637 additions and 1,193 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ linters:
- dogsled
- errcheck
- exportloopref
- gci
# - gci
- gocognit
- goconst
- gocritic
Expand Down
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
module github.com/selectel/go-selvpcclient

go 1.14

require github.com/gophercloud/gophercloud v1.0.0
24 changes: 24 additions & 0 deletions selvpcclient/resell/v2/client.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package v2

import (
"github.com/gophercloud/gophercloud"

"github.com/selectel/go-selvpcclient/selvpcclient"
"github.com/selectel/go-selvpcclient/selvpcclient/resell"
)
Expand Down Expand Up @@ -31,3 +33,25 @@ func NewV2ResellClientWithEndpoint(tokenID, endpoint string) *selvpcclient.Servi

return resellClient
}

func NewOpenstackClient(tokenID string) *gophercloud.ServiceClient {
return &gophercloud.ServiceClient{
ProviderClient: &gophercloud.ProviderClient{
TokenID: tokenID,
HTTPClient: *selvpcclient.NewHTTPClient(),
},
Endpoint: selvpcclient.DefaultOpenstackIdentityEndpoint,
MoreHeaders: map[string]string{"User-agent": selvpcclient.DefaultUserAgent},
}
}

func NewOpenstackClientWithEndpoint(tokenID, endpoint string) *gophercloud.ServiceClient {
return &gophercloud.ServiceClient{
ProviderClient: &gophercloud.ProviderClient{
TokenID: tokenID,
HTTPClient: *selvpcclient.NewHTTPClient(),
},
Endpoint: endpoint,
MoreHeaders: map[string]string{"User-agent": selvpcclient.DefaultUserAgent},
}
}
23 changes: 19 additions & 4 deletions selvpcclient/resell/v2/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,19 @@ package v2
import (
"testing"

"github.com/gophercloud/gophercloud/testhelper"

"github.com/selectel/go-selvpcclient/selvpcclient"
"github.com/selectel/go-selvpcclient/selvpcclient/resell"
"github.com/selectel/go-selvpcclient/selvpcclient/testutils"
)

func TestNewV2ResellClient(t *testing.T) {
token := "fakeID"
const (
token = "fakeID"
endpoint = "http://example.org"
)

func TestNewV2ResellClient(t *testing.T) {
expected := &selvpcclient.ServiceClient{
Endpoint: resell.Endpoint + "/" + APIVersion,
TokenID: token,
Expand All @@ -26,8 +31,6 @@ func TestNewV2ResellClient(t *testing.T) {
}

func TestNewV2ResellClientWithEndpoint(t *testing.T) {
token := "fakeID"
endpoint := "http://example.org"
expected := &selvpcclient.ServiceClient{
Endpoint: endpoint,
TokenID: token,
Expand All @@ -41,3 +44,15 @@ func TestNewV2ResellClientWithEndpoint(t *testing.T) {

testutils.CompareClients(t, expected, actual)
}

func TestNewOpenstackClient(t *testing.T) {
actual := NewOpenstackClient(token)

testhelper.AssertEquals(t, selvpcclient.DefaultOpenstackIdentityEndpoint, actual.Endpoint)
}

func TestNewOpenstackClientWithEndpoint(t *testing.T) {
actual := NewOpenstackClientWithEndpoint(token, endpoint)

testhelper.AssertEquals(t, endpoint, actual.Endpoint)
}
102 changes: 40 additions & 62 deletions selvpcclient/resell/v2/projects/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,79 +4,57 @@ Resell v2 API.
Example of getting a single project referenced by its id
project, _, err := projects.Get(context, resellClient, projectID)
if err != nil {
log.Fatal(err)
}
fmt.Println(project)
project, _, err := projects.Get(context, resellClient, projectID)
if err != nil {
log.Fatal(err)
}
fmt.Println(project)
Example of listing all projects in the domain
allProjects, _, err := projects.List(context, resellClient)
if err != nil {
log.Fatal(err)
}
for _, myProject := range allProjects {
fmt.Println(myProject)
}
allProjects, _, err := projects.List(context, resellClient)
if err != nil {
log.Fatal(err)
}
for _, myProject := range allProjects {
fmt.Println(myProject)
}
Example of creating a single project
createOpts := projects.CreateOpts{
Name: "test000",
Quotas: []quotas.QuotaOpts{
{
Name: "compute_cores",
ResourceQuotasOpts: []quotas.ResourceQuotaOpts{
{
Region: "ru-1",
Zone: "ru-1b",
Value: 1,
},
},
},
{
Name: "compute_ram",
ResourceQuotasOpts: []quotas.ResourceQuotaOpts{
{
Region: "ru-1",
Zone: "ru-1b",
Value: 1024,
},
},
},
},
}
newProject, _, err := projects.Create(context, resellClient, createOpts)
if err != nil {
log.Fatal(err)
}
fmt.Println(newProject)
createOpts := projects.CreateOpts{
Name: "test000",
}
newProject, _, err := projects.Create(context, resellClient, createOpts)
if err != nil {
log.Fatal(err)
}
fmt.Println(newProject)
Example of updating a single project
themeColor := "ffffff"
logo := "123"
themeUpdateOpts := projects.ThemeUpdateOpts{
Color: &themeColor,
Logo: &logo,
}
name := "test001"
updateOpts := projects.UpdateOpts{
Name: &name,
Theme: &themeUpdateOpts,
}
updatedProject, _, err := projects.Update(context, resellClient, newProject.ID, updateOpts)
if err != nil {
log.Fatal(err)
}
fmt.Println(updatedProject)
themeColor := "ffffff"
logo := "123"
themeUpdateOpts := projects.ThemeUpdateOpts{
Color: &themeColor,
Logo: &logo,
}
name := "test001"
updateOpts := projects.UpdateOpts{
Name: &name,
Theme: &themeUpdateOpts,
}
updatedProject, _, err := projects.Update(context, resellClient, newProject.ID, updateOpts)
if err != nil {
log.Fatal(err)
}
fmt.Println(updatedProject)
Example of deleting a single project
_, err = projects.Delete(context, resellClient, newProject.ID)
if err != nil {
log.Fatal(err)
}
_, err = projects.Delete(context, resellClient, newProject.ID)
if err != nil {
log.Fatal(err)
}
*/
package projects
34 changes: 2 additions & 32 deletions selvpcclient/resell/v2/projects/requests_opts.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,52 +2,22 @@ package projects

import (
"encoding/json"

"github.com/selectel/go-selvpcclient/selvpcclient/resell/v2/quotas"
)

// CreateOpts represents options for the project Create request.
type CreateOpts struct {
// Name sets the name for a new project.
Name string `json:"-"`

// Quotas sets quotas for a new project.
Quotas []quotas.QuotaOpts `json:"-"`

// AutoQuotas allows to automatically set quotas for the new project.
// Quota values will be calculated in the Resell V2 service.
AutoQuotas bool `json:"-"`
}

// MarshalJSON implements custom marshalling method for the CreateOpts.
func (opts *CreateOpts) MarshalJSON() ([]byte, error) {
// Return create options with only name and auto_quotas parameters if quotas
// parameter hadn't been provided.
if len(opts.Quotas) == 0 {
return json.Marshal(&struct {
Name string `json:"name"`
AutoQuotas bool `json:"auto_quotas"`
}{
Name: opts.Name,
AutoQuotas: opts.AutoQuotas,
})
}

// Convert opts's quotas update options slice to a map that has resource
// names as keys and resource quotas update options as values.
quotasMap := make(map[string][]quotas.ResourceQuotaOpts, len(opts.Quotas))
for _, quota := range opts.Quotas {
quotasMap[quota.Name] = quota.ResourceQuotasOpts
}

return json.Marshal(&struct {
Name string `json:"name"`
AutoQuotas bool `json:"auto_quotas"`
Quotas map[string][]quotas.ResourceQuotaOpts `json:"quotas"`
Name string `json:"name"`
}{
Name: opts.Name,
AutoQuotas: opts.AutoQuotas,
Quotas: quotasMap,
Name: opts.Name,
})
}

Expand Down
37 changes: 7 additions & 30 deletions selvpcclient/resell/v2/projects/schemas.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package projects

import (
"encoding/json"

"github.com/selectel/go-selvpcclient/selvpcclient/resell/v2/quotas"
)

// Project represents a single Identity service project.
Expand All @@ -25,23 +23,20 @@ type Project struct {

// Theme represents project theme settings.
Theme Theme `json:"-"`

// Quotas contains information about project quotas.
Quotas []quotas.Quota `json:"-"`
}

// UnmarshalJSON implements custom unmarshalling method for the Project type.
func (result *Project) UnmarshalJSON(b []byte) error {
// Populate temporary structure with resource quotas represented as maps.
var s struct {
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Enabled bool `json:"enabled"`
CustomURL string `json:"custom_url"`
Theme Theme `json:"theme"`
Quotas map[string][]quotas.ResourceQuotaEntity `json:"quotas"`
ID string `json:"id"`
Name string `json:"name"`
URL string `json:"url"`
Enabled bool `json:"enabled"`
CustomURL string `json:"custom_url"`
Theme Theme `json:"theme"`
}

err := json.Unmarshal(b, &s)
if err != nil {
return err
Expand All @@ -57,24 +52,6 @@ func (result *Project) UnmarshalJSON(b []byte) error {
Theme: s.Theme,
}

if len(s.Quotas) != 0 {
// Convert resource quota maps to the slice of Quota types.
// Here we're allocating memory in advance because we already know the length
// of a result slice from the JSON bytearray.
resourceQuotasSlice := make([]quotas.Quota, len(s.Quotas))
i := 0
for resourceName, resourceQuotas := range s.Quotas {
resourceQuotasSlice[i] = quotas.Quota{
Name: resourceName,
ResourceQuotasEntities: resourceQuotas,
}
i++
}

// Add the unmarshalled quotas slice to the result.
result.Quotas = resourceQuotasSlice
}

return nil
}

Expand Down
Loading

0 comments on commit 27e2062

Please sign in to comment.