diff --git a/CHANGELOG.md b/CHANGELOG.md index ea82860..4eabf3e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ -## Version 1.5.7 +## Version 1.5.8 ### Bugfix -- Remove the required tag of the start field in the CreateRequest model in cb/lb/create, since it is impossible to create an lb without starting it -- Fix model the RecordGrid, add the ItemGridList model to cloudbroker/grid/models to correctly receive information on get and list requests -- Fix tag json field GID in model RecordResourcesConsumption cb/grid/models \ No newline at end of file +- Fix model the RecordK8CI to cloudbroker/k8ci/models to correctly receive information on get request +- Add model the RecordK8CIList to cloudbroker/k8ci/models to correctly receive information on list request +- Refactored clients to fix the problems with concurrency safety +- Add the Routes field in the CreateInRGRequest and CreateInAccountRequest models in cb/vins. The fields for creating the resource are matched + +For get request to work correctly: +- Fix the Rules field (fix type) in the NATConfig model in cb/vins/models and ca/vins/models +- Fix the InfoVNF model (remove the excess field, add the Routes field) in cb/vins/models +- Add the ItemRoutes model in cb/vins/models +- Fix the RecordVINS model (remove the excess fields) in cb/vins/models diff --git a/README.md b/README.md index 6f4df8d..9cec5db 100644 --- a/README.md +++ b/README.md @@ -62,12 +62,11 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk - `Account` - управление аккаунтами - внутренними учетными записями платформы, которые являются владельцами вычислительных ресурсов; - `BService` - управление группами виртуальных машин (computes); - `Compute` - управление виртуальными машинами (индивидуально); -- `ComputeCI` - управление конвейром для создания виртуальных машин; - `Disks` - управление виртуальными дисками; - `ExtNet` - управление виртуальными сетями, отвечающими за внешний доступ; - `FLIPgroup` - управление группами "плавающими" ip - адресами; - `Image` - управление образами операционных систем; -- `K8CI` - управление конвейром для создания кластера; +- `K8CI` - получение информации о конвейере для создания кластера; - `K8S` - управление кластерами kubernetes; - `KVMPPC` - создание виртуальной машины Power PC (IBM); - `KVMx86` - создание виртуальной машины x86; @@ -84,30 +83,39 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk Данная группа ручек позволяет выполнять следующие операции в платформе: - `Account` - управление аккаунтами - внутренними учетными записями платформы, которые являются владельцами вычислительных ресурсов; +- `APIAccess` - управление доступом к API и его объектам; +- `Backup` - управление резервным копированием; - `Compute` - управление виртуальными машинами (индивидуально); - `Disks` - управление виртуальными дисками; - `ExtNet` - управление виртуальными сетями, отвечающими за внешний доступ; +- `FLIPGroup` - управление группами с «плавающими» ip адресами; - `Grid` - управление площадками; +- `Group` - управление группами пользователей; - `Image` - управление образами операционных систем; - `K8CI` - управление конвейром для создания кластера; - `K8S` - управление кластерами kubernetes; - `KVMPPC` - создание виртуальной машины Power PC (IBM); - `KVMx86` - создание виртуальной машины x86; - `LB` - управление балансировщиками нагрузки; +- `PCIDevice` - управление устройствами; - `RG` - управление ресурсными группами аккаунта; - `SEP` - управление storage endpoint (sep); +- `Stack` - получение информации о стеках; - `Tasks` - получение информации о ходе выполнения асинхронных задач (например, создание кластера); +- `User` - управление пользователями (индивидуально); +- `VGPU` - управление виртуальными графическими процессорами; - `VINS` - управление виртуальными изолированными сетями. ## Работа с библиотекой Алгоритм работы с библиотекой выглядит следующим образом: -1. Настройка конфигурации клиента. -2. Парсинг конфигурации из файла. -3. Создание клиента. -4. Создание структуры запроса. -5. Выполнение запроса. +1. Выполнение одного из действий: + - настройка конфигурации клиента; + - парсинг конфигурации из файла. +2. Создание клиента. +3. Создание структуры запроса. +4. Выполнение запроса. ### Настройка конфигурации клиента @@ -120,7 +128,7 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk | DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие | | Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 | | Timeout | config.Duration | Нет | Таймаут HTTP клиента, по умолчанию - без ограничений | -| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата, по умолчанию - true | +| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата | | Token | string | Нет | JWT токен | #### Пример конфигурации клиента @@ -231,7 +239,6 @@ func main() { - `pkg/cloudapi/account` - для `Account` - `pkg/cloudapi/bservice` - для `Basic Service` - `pkg/cloudapi/compute` - для `Compute` - - `pkg/cloudapi/computeci` - для `ComputeCI` - `pkg/cloudapi/disks` - для `Disks` - `pkg/cloudapi/extnet` - для `ExtNet` - `pkg/cloudapi/flipgroup` - для `FLIPGroup` @@ -248,19 +255,27 @@ func main() { - `pkg/cloudapi/vins` - для `VINS` - **cloudbroker**: - `pkg/cloudbroker/account` - для `Account` + - `pkg/cloudbroker/apiaccess` - для `APIAccess` + - `pkg/cloudbroker/backup` - для `Backup` - `pkg/cloudbroker/compute` - для `Compute` - `pkg/cloudbroker/disks` - для `Disks` - `pkg/cloudbroker/extnet` - для `ExtNet` + - `pkg/cloudbroker/flipgroup` - для `FLIPGroup` - `pkg/cloudbroker/grid` - для `Grid` + - `pkg/cloudbroker/group` - для `Group` - `pkg/cloudbroker/image` - для `Image` - `pkg/cloudbroker/k8ci` - для `K8CI` - `pkg/cloudbroker/k8s` - для `K8S` - `pkg/cloudbroker/kvmppc` - для `KVMPPC` - `pkg/cloudbroker/kvmx86` - для `KVMX86` - `pkg/cloudbroker/lb` - для `LB` + - `pkg/cloudbroker/pcidevice` - для `PCIDevice` - `pkg/cloudbroker/rg` - для `RG` - `pkg/cloudbroker/sep` - для `SEP` + - `pkg/cloudbroker/stack` - для `Stack` - `pkg/cloudbroker/tasks` - для `Tasks` + - `pkg/cloudbroker/user` - для `User` + - `pkg/cloudbroker/vgpu` - для `VGPU` - `pkg/cloudbroker/vins` - для `VINS` Все поля структуры имеют описание, в которых содержится: @@ -405,7 +420,6 @@ func main() { - `.Account()` - для работы с `Account` - `.BService()` - для работы с `BService` - `.Compute()` - для работы с `Compute` - - `.ComputeCI()` - для работы с `ComputeCI` - `.Disks()` - для работы с `Disks` - `.ExtNet()` - для работы с `ExtNet` - `.FLIPgroup()` - для работы с `FLIPGroup` @@ -424,19 +438,27 @@ func main() { Доступные методы для `.CloudBroker()`: - `.Account()` - для работы с `Account` + - `.APIAccess()` - для работы с `APIAccess` + - `.Backup()` - для работы с `Backup` - `.Compute()` - для работы с `Compute` - `.Disks()` - для работы с `Disks` - `.ExtNet()` - для работы с `ExtNet` + - `.FLIPGroup()` - для работы с `FLIPGroup` - `.Grid()` - для работы с `Grid` + - `.Group()` - для работы с `Group` - `.Image()` - для работы с `Image` - `.K8CI()` - для работы с `K8CI` - `.K8S()` - для работы с `K8S` - `.KVMPPC()` - для работы с `KVMPPC` - `.KVMx86()` - для работы с `KVMX86` - `.LB()` - для работы с `LB` + - `.PCIDevice()` - для работы с `PCIDevice` - `.RG()` - для работы с `RG` - `.SEP()` - для работы с `SEP` + - `.Stack()` - для работы с `Stack` - `.Tasks()` - для работы с `Tasks` + - `.User()` - для работы с `User` + - `.VGPU()` - для работы с `VGPU` - `.VINS()` - для работы с `VINS` 3. Вызвать метод, отвечающий за выполнение запроса и передать в него: @@ -448,7 +470,7 @@ func main() { 4. Обработать результат и ошибки. Т.к. все вызовы методов идут последовательно, можно их объеденить в конвейер: -Общий вид вонвейра будет выглядеть так: +Общий вид конвейера будет выглядеть так: ```go client..<группа>.<метод> @@ -705,7 +727,7 @@ func main() { | DecortURL | string | Да | URL адрес платформы, с которой будет осуществляться взаимодействие | | Retries | uint | Нет | Кол-во неудачных попыток выполнения запроса, по умолчанию - 5 | | Timeout | config.Duration | Нет | Таймаут HTTP клиента, по умолчанию - без ограничений | -| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата, по умолчанию - true | +| SSLSkipVerify | bool | Нет | Пропуск проверки подлинности сертификата | | Token | string | Нет | JWT токен | #### Пример конфигурации legacy клиента diff --git a/client.go b/client.go index 9f8ded6..a327d91 100644 --- a/client.go +++ b/client.go @@ -1,24 +1,31 @@ package decortsdk import ( + "bytes" "context" + "crypto/tls" "errors" + "fmt" "io" "net/http" "strings" + "sync" + "time" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" "github.com/google/go-querystring/query" "repository.basistech.ru/BASIS/decort-golang-sdk/config" - "repository.basistech.ru/BASIS/decort-golang-sdk/internal/client" ) // HTTP-client for platform type DecortClient struct { - decortURL string - client *http.Client + decortURL string + client *http.Client + cfg config.Config + expiryTime time.Time + mutex *sync.Mutex } // Сlient builder @@ -27,9 +34,25 @@ func New(cfg config.Config) *DecortClient { cfg.Retries = 5 } + var expiryTime time.Time + + if cfg.Token != "" { + expiryTime = time.Now().AddDate(0, 0, 1) + } + return &DecortClient{ decortURL: cfg.DecortURL, - client: client.NewHttpClient(cfg), + client: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + //nolint:gosec + InsecureSkipVerify: cfg.SSLSkipVerify, + }, + }, + }, + cfg: cfg, + expiryTime: expiryTime, + mutex: &sync.Mutex{}, } } @@ -56,7 +79,11 @@ func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, p return nil, err } - resp, err := dc.client.Do(req) + if err = dc.getToken(ctx); err != nil { + return nil, err + } + + resp, err := dc.do(req) if err != nil { return nil, err } @@ -73,3 +100,64 @@ func (dc *DecortClient) DecortApiCall(ctx context.Context, method, url string, p return respBytes, nil } + +func (dc *DecortClient) getToken(ctx context.Context) error { + dc.mutex.Lock() + defer dc.mutex.Unlock() + + if dc.cfg.Token == "" || time.Now().After(dc.expiryTime) { + body := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s&response_type=id_token", dc.cfg.AppID, dc.cfg.AppSecret) + bodyReader := strings.NewReader(body) + + dc.cfg.SSOURL = strings.TrimSuffix(dc.cfg.SSOURL, "/") + + req, _ := http.NewRequestWithContext(ctx, "POST", dc.cfg.SSOURL+"/v1/oauth/access_token", bodyReader) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, err := dc.client.Do(req) + if err != nil { + return fmt.Errorf("cannot get token: %w", err) + } + + tokenBytes, _ := io.ReadAll(resp.Body) + resp.Body.Close() + + if resp.StatusCode != 200 { + return fmt.Errorf("cannot get token: %s", tokenBytes) + } + + token := string(tokenBytes) + + dc.cfg.Token = token + dc.expiryTime = time.Now().AddDate(0, 0, 1) + } + + return nil +} + +func (dc *DecortClient) do(req *http.Request) (*http.Response, error) { + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Add("Authorization", "bearer "+dc.cfg.Token) + req.Header.Set("Accept", "application/json") + + var resp *http.Response + var err error + buf, _ := io.ReadAll(req.Body) + + for i := uint64(0); i < dc.cfg.Retries; i++ { + req := req.Clone(req.Context()) + req.Body = io.NopCloser(bytes.NewBuffer(buf)) + resp, err = dc.client.Do(req) + + if err == nil { + if resp.StatusCode == 200 { + return resp, err + } + respBytes, _ := io.ReadAll(resp.Body) + err = fmt.Errorf("%s", respBytes) + resp.Body.Close() + } + } + + return nil, fmt.Errorf("could not execute request: %w", err) +} diff --git a/internal/client/http-client.go b/internal/client/http-client.go deleted file mode 100644 index d895df9..0000000 --- a/internal/client/http-client.go +++ /dev/null @@ -1,40 +0,0 @@ -package client - -import ( - "crypto/tls" - "net/http" - "time" - - "repository.basistech.ru/BASIS/decort-golang-sdk/config" -) - -func NewHttpClient(cfg config.Config) *http.Client { - - transCfg := &http.Transport{ - TLSClientConfig: &tls.Config{ - //nolint:gosec - InsecureSkipVerify: cfg.SSLSkipVerify, - }, - } - - var expiredTime time.Time - - if cfg.Token != "" { - expiredTime = time.Now().AddDate(0, 0, 1) - } - - return &http.Client{ - Transport: &transport{ - base: transCfg, - retries: cfg.Retries, - clientID: cfg.AppID, - clientSecret: cfg.AppSecret, - ssoURL: cfg.SSOURL, - token: cfg.Token, - expiryTime: expiredTime, - //TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - }, - - Timeout: cfg.Timeout.Get(), - } -} diff --git a/internal/client/legacy-http-client.go b/internal/client/legacy-http-client.go deleted file mode 100644 index 123df0c..0000000 --- a/internal/client/legacy-http-client.go +++ /dev/null @@ -1,40 +0,0 @@ -package client - -import ( - "crypto/tls" - "net/http" - "net/url" - "time" - - "repository.basistech.ru/BASIS/decort-golang-sdk/config" -) - -// NewLegacyHttpClient creates legacy HTTP Client -func NewLegacyHttpClient(cfg config.LegacyConfig) *http.Client { - transCfg := &http.Transport{ - TLSClientConfig: &tls.Config{ - //nolint:gosec - InsecureSkipVerify: cfg.SSLSkipVerify, - }, - } - - var expiredTime time.Time - - if cfg.Token != "" { - expiredTime = time.Now().AddDate(0, 0, 1) - } - - return &http.Client{ - Transport: &transportLegacy{ - base: transCfg, - username: url.QueryEscape(cfg.Username), - password: url.QueryEscape(cfg.Password), - retries: cfg.Retries, - token: cfg.Token, - decortURL: cfg.DecortURL, - expiryTime: expiredTime, - }, - - Timeout: cfg.Timeout.Get(), - } -} diff --git a/internal/client/legacy-transport.go b/internal/client/legacy-transport.go deleted file mode 100644 index d2ddd1b..0000000 --- a/internal/client/legacy-transport.go +++ /dev/null @@ -1,74 +0,0 @@ -package client - -import ( - "fmt" - "io" - "net/http" - "strings" - "time" -) - -type transportLegacy struct { - base http.RoundTripper - username string - password string - retries uint64 - token string - decortURL string - expiryTime time.Time -} - -func (t *transportLegacy) RoundTrip(request *http.Request) (*http.Response, error) { - if t.token == "" || time.Now().After(t.expiryTime) { - body := fmt.Sprintf("username=%s&password=%s", t.username, t.password) - bodyReader := strings.NewReader(body) - - req, _ := http.NewRequestWithContext(request.Context(), "POST", t.decortURL+"/restmachine/cloudapi/user/authenticate", bodyReader) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - resp, err := t.base.RoundTrip(req) - if err != nil { - return nil, fmt.Errorf("unable to get token: %w", err) - } - - tokenBytes, _ := io.ReadAll(resp.Body) - resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("unable to get token: %s", tokenBytes) - } - - token := string(tokenBytes) - t.token = token - t.expiryTime = time.Now().AddDate(0, 0, 1) - } - - tokenValue := fmt.Sprintf("&authkey=%s", t.token) - tokenReader := strings.NewReader(tokenValue) - - newBody := io.MultiReader(request.Body, tokenReader) - - req, _ := http.NewRequestWithContext(request.Context(), request.Method, request.URL.String(), newBody) - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - req.Header.Set("Accept", "application/json") - - var resp *http.Response - var err error - for i := uint64(0); i < t.retries; i++ { - resp, err = t.base.RoundTrip(req) - if err == nil { - if resp.StatusCode == 200 { - return resp, nil - } - respBytes, _ := io.ReadAll(resp.Body) - err = fmt.Errorf("%s", respBytes) - resp.Body.Close() - } - if err != nil { - return nil, fmt.Errorf("could not execute request: %w", err) - } - time.Sleep(time.Second * 5) - } - return nil, fmt.Errorf("could not execute request: %w", err) -} diff --git a/internal/client/transport.go b/internal/client/transport.go deleted file mode 100644 index 52a1aa4..0000000 --- a/internal/client/transport.go +++ /dev/null @@ -1,69 +0,0 @@ -package client - -import ( - "fmt" - "io" - "net/http" - "strings" - "time" -) - -type transport struct { - base http.RoundTripper - retries uint64 - clientID string - clientSecret string - token string - ssoURL string - expiryTime time.Time -} - -func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { - if t.token == "" || time.Now().After(t.expiryTime) { - body := fmt.Sprintf("grant_type=client_credentials&client_id=%s&client_secret=%s&response_type=id_token", t.clientID, t.clientSecret) - bodyReader := strings.NewReader(body) - - t.ssoURL = strings.TrimSuffix(t.ssoURL, "/") - - req, _ := http.NewRequestWithContext(req.Context(), "POST", t.ssoURL+"/v1/oauth/access_token", bodyReader) - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - - resp, err := t.base.RoundTrip(req) - if err != nil { - return nil, fmt.Errorf("cannot get token: %w", err) - } - - tokenBytes, _ := io.ReadAll(resp.Body) - resp.Body.Close() - - if resp.StatusCode != 200 { - return nil, fmt.Errorf("cannot get token: %s", tokenBytes) - } - - token := string(tokenBytes) - - t.token = token - t.expiryTime = time.Now().AddDate(0, 0, 1) - } - - req.Header.Add("Content-Type", "application/x-www-form-urlencoded") - req.Header.Add("Authorization", "bearer "+t.token) - req.Header.Set("Accept", "application/json") - - var resp *http.Response - var err error - for i := uint64(0); i < t.retries; i++ { - resp, err = t.base.RoundTrip(req) - if err == nil { - if resp.StatusCode == 200 { - return resp, nil - } - respBytes, _ := io.ReadAll(resp.Body) - err = fmt.Errorf("%s", respBytes) - resp.Body.Close() - } - //logrus.Errorf("Could not execute request: %v. Retrying %d/%d", err, i+1, t.retries) - time.Sleep(time.Second * 5) - } - return nil, fmt.Errorf("could not execute request: %w", err) -} diff --git a/legacy-client.go b/legacy-client.go index d1f71c4..6cb8996 100644 --- a/legacy-client.go +++ b/legacy-client.go @@ -1,23 +1,31 @@ package decortsdk import ( + "bytes" "context" + "crypto/tls" "errors" + "fmt" "io" "net/http" + "net/url" "strings" + "sync" + "time" "github.com/google/go-querystring/query" "repository.basistech.ru/BASIS/decort-golang-sdk/config" - "repository.basistech.ru/BASIS/decort-golang-sdk/internal/client" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi" "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker" ) // Legacy HTTP-client for platform type LegacyDecortClient struct { - decortURL string - client *http.Client + decortURL string + client *http.Client + cfg config.LegacyConfig + expiryTime time.Time + mutex *sync.Mutex } // Legacy client builder @@ -26,9 +34,25 @@ func NewLegacy(cfg config.LegacyConfig) *LegacyDecortClient { cfg.Retries = 5 } + var expiryTime time.Time + + if cfg.Token != "" { + expiryTime = time.Now().AddDate(0, 0, 1) + } + return &LegacyDecortClient{ decortURL: cfg.DecortURL, - client: client.NewLegacyHttpClient(cfg), + client: &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + //nolint:gosec + InsecureSkipVerify: cfg.SSLSkipVerify, + }, + }, + }, + cfg: cfg, + expiryTime: expiryTime, + mutex: &sync.Mutex{}, } } @@ -49,13 +73,18 @@ func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url st return nil, err } - body := strings.NewReader(values.Encode()) + if err = ldc.getToken(ctx); err != nil { + return nil, err + } + + body := strings.NewReader(values.Encode() + fmt.Sprintf("&authkey=%s", ldc.cfg.Token)) + req, err := http.NewRequestWithContext(ctx, method, ldc.decortURL+"/restmachine"+url, body) if err != nil { return nil, err } - resp, err := ldc.client.Do(req) + resp, err := ldc.do(req) if err != nil { return nil, err } @@ -72,3 +101,60 @@ func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url st return respBytes, nil } + +func (ldc *LegacyDecortClient) getToken(ctx context.Context) error { + ldc.mutex.Lock() + defer ldc.mutex.Unlock() + + if ldc.cfg.Token == "" || time.Now().After(ldc.expiryTime) { + body := fmt.Sprintf("username=%s&password=%s", url.QueryEscape(ldc.cfg.Username), url.QueryEscape(ldc.cfg.Password)) + bodyReader := strings.NewReader(body) + + req, _ := http.NewRequestWithContext(ctx, "POST", ldc.cfg.DecortURL+"/restmachine/cloudapi/user/authenticate", bodyReader) + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + + resp, err := ldc.client.Do(req) + if err != nil { + return fmt.Errorf("unable to get token: %w", err) + } + + tokenBytes, _ := io.ReadAll(resp.Body) + resp.Body.Close() + + if resp.StatusCode != 200 { + return fmt.Errorf("unable to get token: %s", tokenBytes) + } + + token := string(tokenBytes) + ldc.cfg.Token = token + ldc.expiryTime = time.Now().AddDate(0, 0, 1) + } + + return nil +} + +func (ldc *LegacyDecortClient) do(req *http.Request) (*http.Response, error) { + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + req.Header.Set("Accept", "application/json") + + var resp *http.Response + var err error + buf, _ := io.ReadAll(req.Body) + + for i := uint64(0); i < ldc.cfg.Retries; i++ { + req := req.Clone(req.Context()) + req.Body = io.NopCloser(bytes.NewBuffer(buf)) + resp, err = ldc.client.Do(req) + + if err == nil { + if resp.StatusCode == 200 { + return resp, err + } + respBytes, _ := io.ReadAll(resp.Body) + err = fmt.Errorf("%s", respBytes) + resp.Body.Close() + } + } + + return nil, fmt.Errorf("could not execute request: %w", err) +} diff --git a/pkg/cloudapi/vins/create_in_rg.go b/pkg/cloudapi/vins/create_in_rg.go index 827b273..c01f164 100644 --- a/pkg/cloudapi/vins/create_in_rg.go +++ b/pkg/cloudapi/vins/create_in_rg.go @@ -36,7 +36,7 @@ type CreateInRGRequest struct { // Number of pre created reservations // Required: false - PreReservationsNum uint `url:"preReservationsNum,omitempty" json:"preReservationsNum,omitempty"` + PreReservationsNum uint64 `url:"preReservationsNum,omitempty" json:"preReservationsNum,omitempty"` } // CreateInRG creates VINS in resource group level diff --git a/pkg/cloudapi/vins/models.go b/pkg/cloudapi/vins/models.go index 7d7df0f..8344625 100644 --- a/pkg/cloudapi/vins/models.go +++ b/pkg/cloudapi/vins/models.go @@ -397,9 +397,11 @@ type NATConfig struct { Network string `json:"network"` // List NAT rules - Rules ListNATRules `json:"rules"` + Rules ListNATRulesConfig `json:"rules"` } +type ListNATRulesConfig []ItemNATRule + // Main information about GW type RecordGW struct { // CKey @@ -570,9 +572,6 @@ type RecordVINS struct { // Main information about VNF device VNFDev RecordVNFDev `json:"VNFDev"` - // CKey - CKey string `json:"_ckey"` - // Account ID AccountID uint64 `json:"accountId"` @@ -582,12 +581,24 @@ type RecordVINS struct { // List of VINS computes Computes ListVINSComputes `json:"computes"` + // Created by + CreatedBy string `json:"createdBy"` + + // Created time + CreatedTime uint64 `json:"createdTime"` + // Default GW DefaultGW string `json:"defaultGW"` // Default QOS DefaultQOS QOS `json:"defaultQos"` + // Deleted by + DeletedBy string `json:"deletedBy"` + + // Deleted time + DeletedTime uint64 `json:"deletedTime"` + // Description Description string `json:"desc"` diff --git a/pkg/cloudbroker/k8ci/filter.go b/pkg/cloudbroker/k8ci/filter.go index 7cc2a35..92e8337 100644 --- a/pkg/cloudbroker/k8ci/filter.go +++ b/pkg/cloudbroker/k8ci/filter.go @@ -3,7 +3,7 @@ package k8ci // FilterByID returns ListK8CI with specified ID. func (lkc ListK8CI) FilterByID(id uint64) ListK8CI { predicate := func(ikc ItemK8CI) bool { - return ikc.RecordK8CI.ID == id + return ikc.RecordK8CIList.ID == id } return lkc.FilterFunc(predicate) @@ -12,7 +12,7 @@ func (lkc ListK8CI) FilterByID(id uint64) ListK8CI { // FilterByName returns ListK8CI with specified Name. func (lkc ListK8CI) FilterByName(name string) ListK8CI { predicate := func(ikc ItemK8CI) bool { - return ikc.RecordK8CI.Name == name + return ikc.RecordK8CIList.Name == name } return lkc.FilterFunc(predicate) diff --git a/pkg/cloudbroker/k8ci/filter_test.go b/pkg/cloudbroker/k8ci/filter_test.go index 64f1d7a..fbaa6d4 100644 --- a/pkg/cloudbroker/k8ci/filter_test.go +++ b/pkg/cloudbroker/k8ci/filter_test.go @@ -6,7 +6,7 @@ var k8ciItems = ListK8CI{ Data: []ItemK8CI{ { CreatedTime: 123902139, - RecordK8CI: RecordK8CI{ + RecordK8CIList: RecordK8CIList{ Description: "", GID: 0, GUID: 1, @@ -17,7 +17,7 @@ var k8ciItems = ListK8CI{ MaxMasterCount: 2, MaxWorkerCount: 3, Name: "purple_snake", - SharedWith: []interface{}{}, + SharedWith: []uint64{}, Status: "ENABLED", Version: "1", WorkerDriver: "KVM_X86", @@ -26,7 +26,7 @@ var k8ciItems = ListK8CI{ }, { CreatedTime: 123902232, - RecordK8CI: RecordK8CI{ + RecordK8CIList: RecordK8CIList{ Description: "", GID: 0, GUID: 2, @@ -37,7 +37,7 @@ var k8ciItems = ListK8CI{ MaxMasterCount: 3, MaxWorkerCount: 5, Name: "green_giant", - SharedWith: []interface{}{}, + SharedWith: []uint64{}, Status: "DISABLED", Version: "2", WorkerDriver: "KVM_X86", @@ -46,7 +46,7 @@ var k8ciItems = ListK8CI{ }, { CreatedTime: 123902335, - RecordK8CI: RecordK8CI{ + RecordK8CIList: RecordK8CIList{ Description: "", GID: 0, GUID: 3, @@ -57,7 +57,7 @@ var k8ciItems = ListK8CI{ MaxMasterCount: 5, MaxWorkerCount: 9, Name: "magenta_cloud", - SharedWith: []interface{}{}, + SharedWith: []uint64{}, Status: "ENABLED", Version: "3", WorkerDriver: "KVM_X86", diff --git a/pkg/cloudbroker/k8ci/models.go b/pkg/cloudbroker/k8ci/models.go index 0b131cd..647956b 100644 --- a/pkg/cloudbroker/k8ci/models.go +++ b/pkg/cloudbroker/k8ci/models.go @@ -1,11 +1,11 @@ package k8ci -// Main information about K8CI +// Main information about K8CI in List type ItemK8CI struct { // Created time CreatedTime uint64 `json:"createdTime"` // Detailed information about K8CI - RecordK8CI + RecordK8CIList } // List K8CI @@ -17,7 +17,55 @@ type ListK8CI struct { EntryCount uint64 `json:"entryCount"` } -// Detailed information about K8CI +// Detailed information about K8CI in List +type RecordK8CIList struct { + // Description + Description string `json:"desc"` + + // Grid ID + GID uint64 `json:"gid"` + + // GUID + GUID uint64 `json:"guid"` + + // ID + ID uint64 `json:"id"` + + // Load balancer image ID + LBImageID uint64 `json:"lbImageId"` + + // Master driver + MasterDriver string `json:"masterDriver"` + + // Master image ID + MasterImageID uint64 `json:"masterImageId"` + + // Max master count + MaxMasterCount uint64 `json:"maxMasterCount"` + + // Max worker count + MaxWorkerCount uint64 `json:"maxWorkerCount"` + + // Name + Name string `json:"name"` + + // Shared with + SharedWith []uint64 `json:"sharedWith"` + + // Status + Status string `json:"status"` + + // Version + Version string `json:"version"` + + // Worker driver + WorkerDriver string `json:"workerDriver"` + + // Worker image ID + WorkerImageID uint64 `json:"workerImageId"` +} + +// Detailed information about K8CI type RecordK8CI struct { // Description Description string `json:"desc"` @@ -52,8 +100,11 @@ type RecordK8CI struct { // Name Name string `json:"name"` + //NetworkPlugins + NetworkPlugins []string `json:"networkPlugins"` + // Shared with - SharedWith []interface{} `json:"sharedWith"` + SharedWith []uint64 `json:"sharedWith"` // Status Status string `json:"status"` diff --git a/pkg/cloudbroker/vins/create_in_account.go b/pkg/cloudbroker/vins/create_in_account.go index d4bbfac..04375c7 100644 --- a/pkg/cloudbroker/vins/create_in_account.go +++ b/pkg/cloudbroker/vins/create_in_account.go @@ -2,12 +2,24 @@ package vins import ( "context" + "encoding/json" "net/http" "strconv" "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators" ) +type Route struct { + // Destination network + Destination string `url:"destination" json:"destination" validate:"required"` + + //Destination network mask in 255.255.255.255 format + Netmask string `url:"netmask" json:"netmask" validate:"required"` + + //Next hop host, IP address from ViNS ID free IP pool + Gateway string `url:"gateway" json:"gateway" validate:"required"` +} + // Request struct for create VINS in account type CreateInAccountRequest struct { // VINS name @@ -34,11 +46,20 @@ type CreateInAccountRequest struct { // Required: false PreReservationsNum uint64 `url:"preReservationsNum,omitempty" json:"preReservationsNum,omitempty"` + // List of static routes, each item must have destination, netmask, and gateway fields + // Required: false + Routes []Route `url:"-" json:"routes,omitempty" validate:"omitempty,dive"` + // Reason for action // Required: false Reason string `url:"reason,omitempty" json:"reason,omitempty"` } +type wrapperCreateRequestInAcc struct { + CreateInAccountRequest + Routes []string `url:"routes,omitempty"` +} + // CreateInAccount creates VINS in account level func (v VINS) CreateInAccount(ctx context.Context, req CreateInAccountRequest) (uint64, error) { err := validators.ValidateRequest(req) @@ -48,9 +69,31 @@ func (v VINS) CreateInAccount(ctx context.Context, req CreateInAccountRequest) ( } } + var routes []string + + if len(req.Routes) != 0 { + routes = make([]string, 0, len(req.Routes)) + + for r := range req.Routes { + b, err := json.Marshal(req.Routes[r]) + if err != nil { + return 0, err + } + + routes = append(routes, string(b)) + } + } else { + routes = []string{"[]"} + } + + reqWrapped := wrapperCreateRequestInAcc{ + CreateInAccountRequest: req, + Routes: routes, + } + url := "/cloudbroker/vins/createInAccount" - res, err := v.client.DecortApiCall(ctx, http.MethodPost, url, req) + res, err := v.client.DecortApiCall(ctx, http.MethodPost, url, reqWrapped) if err != nil { return 0, err } diff --git a/pkg/cloudbroker/vins/create_in_rg.go b/pkg/cloudbroker/vins/create_in_rg.go index 01232f9..28af320 100644 --- a/pkg/cloudbroker/vins/create_in_rg.go +++ b/pkg/cloudbroker/vins/create_in_rg.go @@ -2,6 +2,7 @@ package vins import ( "context" + "encoding/json" "net/http" "strconv" @@ -36,13 +37,22 @@ type CreateInRGRequest struct { // Number of pre created reservations // Required: false - PreReservationsNum uint `url:"preReservationsNum,omitempty" json:"preReservationsNum,omitempty"` + PreReservationsNum uint64 `url:"preReservationsNum,omitempty" json:"preReservationsNum,omitempty"` + + // List of static routes, each item must have destination, netmask, and gateway fields + // Required: false + Routes []Route `url:"-" json:"routes,omitempty" validate:"omitempty,dive"` // Reason for action // Required: false Reason string `url:"reason,omitempty" json:"reason,omitempty"` } +type wrapperCreateRequestInRG struct { + CreateInRGRequest + Routes []string `url:"routes,omitempty"` +} + // CreateInRG creates VINS in resource group level func (v VINS) CreateInRG(ctx context.Context, req CreateInRGRequest) (uint64, error) { err := validators.ValidateRequest(req) @@ -51,10 +61,31 @@ func (v VINS) CreateInRG(ctx context.Context, req CreateInRGRequest) (uint64, er return 0, validators.ValidationError(validationError) } } + var routes []string + + if len(req.Routes) != 0 { + routes = make([]string, 0, len(req.Routes)) + + for r := range req.Routes { + b, err := json.Marshal(req.Routes[r]) + if err != nil { + return 0, err + } + + routes = append(routes, string(b)) + } + } else { + routes = []string{"[]"} + } + + reqWrapped := wrapperCreateRequestInRG{ + CreateInRGRequest: req, + Routes: routes, + } url := "/cloudbroker/vins/createInRG" - res, err := v.client.DecortApiCall(ctx, http.MethodPost, url, req) + res, err := v.client.DecortApiCall(ctx, http.MethodPost, url, reqWrapped) if err != nil { return 0, err } diff --git a/pkg/cloudbroker/vins/models.go b/pkg/cloudbroker/vins/models.go index af4a994..7658d87 100644 --- a/pkg/cloudbroker/vins/models.go +++ b/pkg/cloudbroker/vins/models.go @@ -345,7 +345,7 @@ type NATConfig struct { Network string `json:"network"` // Rules - Rules []interface{} `json:"rules"` + Rules []ItemNATRule `json:"rules"` } // Main information about NAT @@ -357,7 +357,7 @@ type RecordNAT struct { InfoVNF } -// NAT/GW/NAT details +// DHCP/GW/NAT details type InfoVNF struct { // CKey CKey string `json:"_ckey"` @@ -368,9 +368,6 @@ type InfoVNF struct { // Account ID AccountID uint64 `json:"accountId"` - // Config - Config NATConfig `json:"config"` - // CreatedTime CreatedTime uint64 `json:"createdTime"` @@ -401,6 +398,9 @@ type InfoVNF struct { // Pure virtual PureVirtual bool `json:"pureVirtual"` + // Routes + Routes ListRoutes `json:"routes"` + // Status Status string `json:"status"` @@ -411,6 +411,28 @@ type InfoVNF struct { Type string `json:"type"` } +type ListRoutes []ItemRoutes + +type ItemRoutes struct { + //Compute Id + ComputeIds []uint64 `json:"computeIds"` + + // Destination network + Destination string `json:"destination"` + + //Next hop host, IP address from ViNS ID free IP pool + Gateway string `json:"gateway"` + + // GUID + GUID uint64 `json:"guid"` + + // ID + ID uint64 `json:"id"` + + //Destination network mask in 255.255.255.255 format + Netmask string `json:"netmask"` +} + // main information about VNF type RecordVNFs struct { // DHCP @@ -428,12 +450,6 @@ type RecordVINS struct { // VNF device VNFDev VNFDev `json:"VNFDev"` - // CKey - CKey string `json:"_ckey"` - - // Meta - Meta []interface{} `json:"_meta"` - // Account ID AccountID uint64 `json:"accountId"`