Compare commits

...

7 Commits

Author SHA1 Message Date
a267d35ddf v1.12.10 2025-11-18 14:42:39 +03:00
asteam
0bf073da93 v1.12.9 2025-11-14 17:38:59 +03:00
asteam
562b6019d0 v1.12.8 2025-10-14 16:44:25 +03:00
bf5c01b40b v1.12.7 2025-10-09 17:41:15 +03:00
dayterr
095a18f27a v1.12.6 2025-10-02 12:36:47 +03:00
asteam
84a090f9e8 v1.12.5 2025-09-26 12:21:29 +03:00
befff7acd9 V1.12.4 2025-09-19 13:45:58 +03:00
203 changed files with 12933 additions and 376 deletions

View File

@@ -1,75 +1,21 @@
## Version 1.12.3
## Version 1.12.10
Методы `Audits` в cloudapi/compute, cloudbroker/compute, cloudapi/account, cloudbroker/account, cloudapi/vins, cloudbroker/vins, cloudapi/rg и cloudbroker/rg стали deprecated и в следующих версиях будут удалены, вместо них необходимо использовать метод `List` в cloudapi/audit и cloudbroker/audit с соответствующими фильтрами
Метод `ListStacks` в cloudbroker/image стал deprecated и в следующих версиях будет удалён
Методы `AccessGrant`, `AccessGrantToPool`, `AccessRevoke`, `AccessRevokeToPool` в cloudbroker/sep стали deprecated и в следующих версиях будут удалены
Все методы группы `.SDN()` находятся в альфа-версии.
### Добавлено
#### account
#### ClientInterface
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-606 | Поле `policies` в структуру ответа `Resource` в cloudapi/account и в cloudbroker/account |
#### compute
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-603 | Поле `OSVersion` в структуры ответа `RecordCompute` и `ItemCompute` в cloudapi/compute и `RecordCompute` и `InfoCompute` в cloudbroker/compute |
| BGOS-603 | Опциональное поле `OSVersion` в структуры запроса `RedeployRequest` и `UpdateRequest` в cloudapi/compute и cloudbroker/compute |
| BGOS-611 | Методы `StartMigrationIN`, `StopMigrationIN`, `MigrateStorageList` и структуры запросов `StartMigrationINRequest`, `StopMigrationINRequest` и `MigrateStorageListRequest` в cloudbroker/compute |
#### disks
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-609 | Опциональное поле `StoragePolicyID` в структуры запроса `ListUnattachedRequest` и `ListRequest` в cloudapi/disk и cloudbroker/disk |
#### kvmx86
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-603 | Опциональное поле `OSVersion` в структуры запроса `CreateRequest` и `CreateBlankRequest` в cloudapi/compute и cloudbroker/compute |
| BGOS-603 | Опциональное поле `OSVersion` в структуру запроса `MassCreateRequest` в cloudbroker/compute |
#### node
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-608 | Методы `AddSSHIdentity`, `GetSSHIdentity` и структуры запросов `AddSSHIdentityRequest` и `GetSSHIdentityRequest` в cloudbroker/node |
#### security group
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-601 | Добавлены комментарии к структурам ответов |
#### storage policy
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-598 | Поле `sep_name` в структуре ответа `AccessSEPPool` в cloudapi/stpolicy и в cloudbroker/stpolicy |
| BGOS-600 | Добавлены комментарии к структурам ответов |
### Исправлено
#### account
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-607 | Поле `StoragePolicies` в структуре запроса `CreateRequest` стало опциональным в cloudbroker/account |
| BGOS-607 | Изменен тип поля `ID` в структуре запроса `StoragePolicy` с int64 на uint64 в cloudbroker/account |
#### rg
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-607 | Изменен тип поля `ID` в структуре запроса `StoragePolicy` с int64 на uint64 в cloudapi/rg и в cloudbroker/rg |
| BGOS-607 | Поле `StoragePolicies` в структуре запроса `CreateRequest` стало опциональным в cloudbroker/account |
| --- | ---|
| BGOS-673 | Добавлен mock для интерфейса ClientInterface с примерами |
### Удалено
#### account
#### logical ports
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-605 | Поле `CKey` из структуры ответа `RecordAccount` в cloudapi/account и поля `CKey` из структуры ответа `InfoAccount` и `Meta` из структуры ответа `ItemAccount` в cloudbroker/account |
#### compute
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-603 | Поля `VirtualImageID`, `VirtualImageName` из структуры ответа `RecordCompute` и `ItemCompute` в cloudapi/compute и `RecordCompute` и `InfoCompute` в cloudbroker/compute |
| BGOS-603 | Поля `ImageID` из структуры ответа `ItemCompute` в cloudapi/compute и `InfoCompute` в cloudbroker/compute |
| BGOS-612 | Поле `ImageID` из структуры запроса `ListRequest` в cloudbroker/compute |
#### image
| Идентификатор<br>задачи | Описание |
| --- | --- |
| BGOS-604 | Метод `UpdateNodes` и структура запроса `UpdateNodesRequest` в cloudbroker/image |
| BGOS-668 | Поля `LogicalPortID` и `VersionID` в структуре запроса `CreateRequest` |

View File

@@ -27,6 +27,7 @@ Decort SDK - это библиотека, написанная на языке G
- [Список API](#список-api)
- [Cloudapi](#cloudapi)
- [Cloudbroker](#cloudbroker)
- [SDN](#sdn)
- [Работа с библиотекой](#работа-с-библиотекой)
- [Настройка конфигурации клиента](#настройка-конфигурации-клиента)
- [Пример конфигурации клиента](#пример-конфигурации-клиента)
@@ -82,6 +83,8 @@ Decort SDK - это библиотека, написанная на языке G
- [Пример выполнения запроса](#пример-выполнения-запроса-4)
- [Проверка соответствия версии платформы и версии dynamix](#проверка-соответствия-версии-платформы-и-версии-dynamix)
- [Пример выполнения запроса](#пример-выполнения-запроса-5)
- [Создание mock клиента](#создание-mock-клиента)
- [Пример создания mock клиента](#пример-создания-mock-клиента)
## Установка
@@ -174,7 +177,16 @@ go get -u repository.basistech.ru/BASIS/decort-golang-sdk
`SDN` позволяет выполнять запросы к группе пользовательских конечных точек
Данная группа ручек позволяет выполнять следующие операции в платформе:
- `AccessGroup` - управление группами доступа
- `Access group` - управление группами доступа;
- `Address pool` - управление пулами адресов;
- `DefaultSecurityPolicies` - управление политиками хранения по умолчанию;
- `ExtNet` - управление виртуальными сетями, отвечающими за внешний доступ;
- `FloatingIPs` - управление плавающими IP-адресами;
- `Logical ports` - управление логическими портами;
- `NetworkObjectGroups` - управление группами объектов сети;
- `Routers` - управление роутерами;
- `SecurityPolicies` - управление политиками хранения;
- `Segments` - управление сегментами;
## Работа с библиотекой
@@ -367,7 +379,16 @@ func main() {
- `pkg/cloudbroker/vins` - для `VINS`
- `pkg/cloudbroker/zone` - для `Zone`
- **sdn**:
- `pkg/sdn/access_groups` - для `AccessGroups`
- `pkg/sdn/acsgroups` - для `Access groups`
- `pkg/sdn/adrspools` - для `Address pool`
- `pkg/sdn/defsecpolicies` - для `DefaultSecurityPolicies`
- `pkg/sdn/external_networks` - для `ExtNet`
- `pkg/sdn/flips` - для `FloatingIPs`
- `pkg/sdn/logicalports` - для `Logical ports`
- `pkg/sdn/netobjgroups` - для `NetworkObjectGroups`
- `pkg/sdn/routers` - для `Routers`
- `pkg/sdn/secpolicies` - для `SecurityPolicies`
- `pkg/sdn/segments` - для `Segments`
Все поля структуры имеют описание, в которых содержится:
@@ -570,7 +591,16 @@ func main() {
Доступные методы для `.SDN()`:
- `.AccessGroup()` - для работы с `AccessGroup`
- `.AccessGroup()` - для работы с `Access group`
- `.AddressPool()` - для работы с `Addres pool`
- `.DefaultSecurityPolicies()` - для работы с `DefaultSecurityPolicies`
- `.ExtNet()` - для работы с `ExtNet`
- `.FloatingIPs()` - для работы с `FloatingIPs`
- `.LogicalPorts()` - для работы с `Logical ports`
- `.NetworkObjectGroups()` - для работы с `NetworkObjectGroups`
- `.Routers()` - для работы с `Routers`
- `.SecurityPolicies()` - для работы с `SecurityPolicies`
- `.Segments()` - для работы с `Segments`
3. Вызвать метод, отвечающий за выполнение запроса и передать в него:
@@ -1681,4 +1711,35 @@ func main(){
// Проверка соответствия версии
checkInfo, err := client.Check()
}
```
# Создание mock клиента
Создание клиента происходит с помощью функции-строителя `NewMockDecortClient` из основного пакета `decort-sdk`. Функция принимает mock реализацию интерфейса interfaces.Calller, возвращает структуру `MockDecortClient`, с помощью которой можно производить unit тестирование API Decort SDK без подключения к серверу
#### Пример создания mock клиента
```go
package unit_test
import (
"testing"
"go.uber.org/mock/gomock"
decortsdk "repository.basistech.ru/BASIS/decort-golang-sdk"
)
// Пример юнит тестирования на моках
func TestClient(t *testing.T) {
ctrl := gomock.NewController(t)
// Создаем mock интерфейса Caller
mockCaller := decortsdk.NewMockCaller(ctrl)
// Создаем mock интерфейса DecortClient
mockClient := decortsdk.NewMockDecortClient(mockCaller)
// ....
}
```
Пример юнит теста можно посмотреть в файле [samples/client/client_test.go](samples/client/client_test.go)
При редактировании интерфеса interface.Caller необходимо перегенерировать Mock :
``` shell
make gen-mock
```

View File

@@ -28,7 +28,13 @@ func (de DecortClient) Check() (*CheckInfo, error) {
err = json.Unmarshal([]byte(strings.Replace(strings.Trim(string(res), `"`), "\\", "", -1)), &info)
if err != nil {
return nil, err
var v string
json.Unmarshal([]byte(res), &v)
if _, exists := constants.VersionMap[v]; exists {
info.Version = v
} else {
return nil, fmt.Errorf("platform version isn't supported")
}
}
if v, ok := constants.VersionMap[info.Version]; ok {
@@ -51,7 +57,13 @@ func (bvs BVSDecortClient) Check() (*CheckInfo, error) {
err = json.Unmarshal([]byte(strings.Replace(strings.Trim(string(res), `"`), "\\", "", -1)), &info)
if err != nil {
return nil, err
var v string
json.Unmarshal([]byte(res), &v)
if _, exists := constants.VersionMap[v]; exists {
info.Version = v
} else {
return nil, fmt.Errorf("platform version isn't supported")
}
}
if v, ok := constants.VersionMap[info.Version]; ok {
@@ -74,7 +86,13 @@ func (ldc LegacyDecortClient) Check() (*CheckInfo, error) {
err = json.Unmarshal([]byte(strings.Replace(strings.Trim(string(res), `"`), "\\", "", -1)), &info)
if err != nil {
return nil, err
var v string
json.Unmarshal([]byte(res), &v)
if _, exists := constants.VersionMap[v]; exists {
info.Version = v
} else {
return nil, fmt.Errorf("platform version isn't supported")
}
}
if v, ok := constants.VersionMap[info.Version]; ok {

View File

@@ -288,7 +288,7 @@ func (dc *DecortClient) do(req *http.Request, ctype string) ([]byte, error) {
// handle successful request
respBytes, _ := io.ReadAll(resp.Body)
if resp.StatusCode == 200 {
if resp.StatusCode == 200 || resp.StatusCode == 204 {
return respBytes, nil
}

View File

@@ -18,6 +18,7 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn"
)
// BVSDecortClient is HTTP-client for platform
@@ -69,6 +70,11 @@ func (bdc *BVSDecortClient) CloudBroker() *cloudbroker.CloudBroker {
return cloudbroker.New(bdc)
}
// SDN builder
func (bdc *BVSDecortClient) SDN() *sdn.SDN {
return sdn.New(bdc)
}
// DecortApiCall method for sending requests to the platform
func (bdc *BVSDecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) {
var body *bytes.Buffer

32
client_mock.go Normal file
View File

@@ -0,0 +1,32 @@
package decortsdk
import (
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn"
)
type MockDecortClient struct {
apiCaller *MockCaller
}
func NewMockDecortClient(apiCaller *MockCaller) ClientInterface {
return &MockDecortClient{
apiCaller: apiCaller,
}
}
// CloudAPI builder
func (mdc *MockDecortClient) CloudAPI() *cloudapi.CloudAPI {
return cloudapi.New(mdc.apiCaller)
}
// CloudBroker builder
func (mdc *MockDecortClient) CloudBroker() *cloudbroker.CloudBroker {
return cloudbroker.New(mdc.apiCaller)
}
// SDN builder
func (mdc *MockDecortClient) SDN() *sdn.SDN {
return sdn.New(mdc.apiCaller)
}

86
client_mock_gen.go Normal file
View File

@@ -0,0 +1,86 @@
// Code generated by MockGen. DO NOT EDIT.
// Source: interfaces/caller.go
//
// Generated by this command:
//
// mockgen -package decortsdk -source interfaces/caller.go
//
// Package decortsdk is a generated GoMock package.
package decortsdk
import (
context "context"
reflect "reflect"
gomock "go.uber.org/mock/gomock"
)
// MockCaller is a mock of Caller interface.
type MockCaller struct {
ctrl *gomock.Controller
recorder *MockCallerMockRecorder
isgomock struct{}
}
// MockCallerMockRecorder is the mock recorder for MockCaller.
type MockCallerMockRecorder struct {
mock *MockCaller
}
// NewMockCaller creates a new mock instance.
func NewMockCaller(ctrl *gomock.Controller) *MockCaller {
mock := &MockCaller{ctrl: ctrl}
mock.recorder = &MockCallerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockCaller) EXPECT() *MockCallerMockRecorder {
return m.recorder
}
// DecortApiCall mocks base method.
func (m *MockCaller) DecortApiCall(ctx context.Context, method, url string, params any) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecortApiCall", ctx, method, url, params)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecortApiCall indicates an expected call of DecortApiCall.
func (mr *MockCallerMockRecorder) DecortApiCall(ctx, method, url, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecortApiCall", reflect.TypeOf((*MockCaller)(nil).DecortApiCall), ctx, method, url, params)
}
// DecortApiCallCtype mocks base method.
func (m *MockCaller) DecortApiCallCtype(ctx context.Context, method, url, ctype string, params any) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecortApiCallCtype", ctx, method, url, ctype, params)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecortApiCallCtype indicates an expected call of DecortApiCallCtype.
func (mr *MockCallerMockRecorder) DecortApiCallCtype(ctx, method, url, ctype, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecortApiCallCtype", reflect.TypeOf((*MockCaller)(nil).DecortApiCallCtype), ctx, method, url, ctype, params)
}
// DecortApiCallMP mocks base method.
func (m *MockCaller) DecortApiCallMP(ctx context.Context, method, url string, params any) ([]byte, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "DecortApiCallMP", ctx, method, url, params)
ret0, _ := ret[0].([]byte)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DecortApiCallMP indicates an expected call of DecortApiCallMP.
func (mr *MockCallerMockRecorder) DecortApiCallMP(ctx, method, url, params any) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DecortApiCallMP", reflect.TypeOf((*MockCaller)(nil).DecortApiCallMP), ctx, method, url, params)
}

18
go.mod
View File

@@ -1,21 +1,25 @@
module repository.basistech.ru/BASIS/decort-golang-sdk
go 1.20
go 1.24.0
require (
github.com/go-playground/validator/v10 v10.11.2
github.com/go-playground/validator/v10 v10.28.0
github.com/google/go-querystring v1.1.0
github.com/joho/godotenv v1.5.1
github.com/stretchr/testify v1.9.0
go.uber.org/mock v0.6.0
gopkg.in/yaml.v3 v3.0.1
)
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.10 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/google/go-cmp v0.5.9 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.2.1 // indirect
golang.org/x/crypto v0.15.0 // indirect
golang.org/x/sys v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/crypto v0.42.0 // indirect
golang.org/x/sys v0.36.0 // indirect
golang.org/x/text v0.29.0 // indirect
)

40
go.sum
View File

@@ -1,13 +1,15 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.10 h1:zyueNbySn/z8mJZHLt6IPw0KoZsiQNszIpU+bX4+ZK0=
github.com/gabriel-vasile/mimetype v1.4.10/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.11.2 h1:q3SHpufmypg+erIExEKUmsgmhDTyhcJ38oeKGACXohU=
github.com/go-playground/validator/v10 v10.11.2/go.mod h1:NieE624vt4SCTJtD87arVLvdmjPAeV8BQlHtMnw9D7s=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -15,26 +17,22 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w=
github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
golang.org/x/crypto v0.15.0 h1:frVn1TEaCEaZcn3Tmd7Y2b5KKPaZ+I32Q2OA3kYp5TA=
golang.org/x/crypto v0.15.0/go.mod h1:4ChreQoLWfG3xLDer1WdlH5NdlQ3+mwnQq1YTKY+72g=
golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q=
golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -28,5 +28,9 @@ var FileName = map[string]string{
var K8sValues = []string{"labels", "taints", "annotations, additionalSANs"}
var VersionMap = map[string]string{
"4.4.0": "-",
"4.3.0": "-",
"4.2.0": "-",
"4.1.1": "-",
"4.1.0": "-",
}

View File

@@ -165,11 +165,11 @@ func flipgroupClientTypeValidator(fe validator.FieldLevel) bool {
return IsInSlice(fieldValue, flipgroupClientTypeValues)
}
// kvmNetTypeValidator is used to validate NetType field.
func kvmNetTypeValidator(fe validator.FieldLevel) bool {
// massCreateTypeValidator is used to validate net type field when mass creating kvm
func massCreateTypeValidator(fe validator.FieldLevel) bool {
fieldValue := fe.Field().String()
return IsInSlice(fieldValue, kvmNetTypeValues)
return IsInSlice(fieldValue, massCreateNetTypeValues)
}
// lbAlgorithmValidator is used to validate Algorithm field.
@@ -426,6 +426,13 @@ func deviceValidator(fe validator.FieldLevel) bool {
return IsInSlice(fieldValue, deviceValues)
}
// ipTypesValidator is used to validate ip types version fields
func ipTypesValidator(fe validator.FieldLevel) bool {
fieldValue := fe.Field().String()
return IsInSlice(fieldValue, ipTypeValues)
}
// ValidateRAM checks if request contains RAM value that is positive integer divisible by divisibility passed.
// It is recommended to pass constants.RAM_DIVISIBILITY as divisility arguement
func ValidateRAM(r interfaces.RequestWithRAM, divisibility uint64) error {
@@ -457,3 +464,10 @@ func trunkTagsValidator(fe validator.FieldLevel) bool {
}
return uint64(numFieldValue) >= uint64(trunkTagsMin) && uint64(numFieldValue) <= uint64(trunkTagsMax)
}
// addressPoolNetTypeValidator is used to validate NetAddressType fields
func addressPoolNetTypeValidator(fe validator.FieldLevel) bool {
fieldValue := fe.Field().String()
return IsInSlice(fieldValue, addressPoolNetTypeValues)
}

View File

@@ -142,12 +142,12 @@ func errorMessage(fe validator.FieldError) string {
prefix,
fe.Field())
// KVM_X86 Validators
case "kvmNetType":
// KVM_X86 Mass create validators
case "massCreateNetType":
return fmt.Sprintf("%s %s must be one of the following: %s",
prefix,
fe.Field(),
joinValues(kvmNetTypeValues))
joinValues(massCreateNetTypeValues))
// LB Validators
case "lbAlgorithm":
@@ -348,11 +348,24 @@ func errorMessage(fe validator.FieldError) string {
prefix,
fe.Field())
// addressPoolNetTypeValidator validator
case "addressPoolNetTypeValidator":
return fmt.Sprintf("%s %s must be one of the following: %s",
prefix,
fe.Field(),
joinValues(addressPoolNetTypeValues))
case "device":
return fmt.Sprintf("%s %s must be one of the following: %s",
prefix,
fe.Field(),
joinValues(deviceValues))
case "ipTypes":
return fmt.Sprintf("%s %s must be one of the following: %s",
prefix,
fe.Field(),
joinValues(ipTypeValues))
}
return fe.Error()

View File

@@ -121,7 +121,7 @@ func registerAllValidators(validate *validator.Validate) error {
return err
}
err = validate.RegisterValidation("kvmNetType", kvmNetTypeValidator)
err = validate.RegisterValidation("massCreateNetType", massCreateTypeValidator)
if err != nil {
return err
}
@@ -301,5 +301,15 @@ func registerAllValidators(validate *validator.Validate) error {
return err
}
err = validate.RegisterValidation("addressPoolNetTypeValidator", addressPoolNetTypeValidator)
if err != nil {
return err
}
err = validate.RegisterValidation("ipTypes", ipTypesValidator)
if err != nil {
return err
}
return nil
}

View File

@@ -24,8 +24,8 @@ var (
flipgroupClientTypeValues = []string{"compute", "vins"}
kvmNetTypeValues = []string{"EXTNET", "VINS", "NONE"}
kvmx86NetTypeValues = []string{"EXTNET", "VINS", "EMPTY", "VFNIC", "DPDK", "SDN", "TRUNK"}
massCreateNetTypeValues = []string{"EXTNET", "VINS", "TRUNK"}
kvmx86NetTypeValues = []string{"EXTNET", "VINS", "EMPTY", "VFNIC", "DPDK", "SDN", "TRUNK"}
lbAlgorithmValues = []string{"roundrobin", "static-rr", "leastconn"}
@@ -77,6 +77,10 @@ var (
securityGroupDirectionValues = []string{"inbound", "outbound"}
securityGroupEthertypeValues = []string{"IPv4", "IPv6"}
securityGroupProtocolValues = []string{"icmp", "tcp", "udp"}
addressPoolNetTypeValues = []string{"IPv4", "IPv6", "MAC"}
ipTypeValues = []string{"v4, v6"}
)
const (

View File

@@ -18,6 +18,7 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn"
)
// LegacyDecortClient is Legacy HTTP-client for platform
@@ -67,6 +68,11 @@ func (ldc *LegacyDecortClient) CloudBroker() *cloudbroker.CloudBroker {
return cloudbroker.New(ldc)
}
// SDN builder
func (ldc *LegacyDecortClient) SDN() *sdn.SDN {
return sdn.New(ldc)
}
// DecortApiCall method for sending requests to the platform
func (ldc *LegacyDecortClient) DecortApiCall(ctx context.Context, method, url string, params interface{}) ([]byte, error) {
// get token

View File

@@ -0,0 +1,81 @@
package audit
// FilterByID returns ListAudits with specified ID.
func (la ListAudits) FilterByID(guid string) ListAudits {
predicate := func(ia ItemAudit) bool {
return ia.GUID == guid
}
return la.FilterFunc(predicate)
}
// FilterByCall returns ListAudits with specified call.
func (la ListAudits) FilterByCall(call string) ListAudits {
predicate := func(ic ItemAudit) bool {
return ic.Call == call
}
return la.FilterFunc(predicate)
}
// FilterByCorrelationID returns ListAudits with specified correlation id.
func (la ListAudits) FilterByCorrelationID(correlationID string) ListAudits {
predicate := func(ic ItemAudit) bool {
return ic.CorrelationID == correlationID
}
return la.FilterFunc(predicate)
}
// FilterByRemoteAddr returns ListAudits with specified remote address.
func (la ListAudits) FilterByRemoteAddr(remoteAddr string) ListAudits {
predicate := func(ic ItemAudit) bool {
return ic.RemoteAddr == remoteAddr
}
return la.FilterFunc(predicate)
}
// FilterByUser returns ListAudits with specified user name.
func (la ListAudits) FilterByUser(user string) ListAudits {
predicate := func(ic ItemAudit) bool {
return ic.User == user
}
return la.FilterFunc(predicate)
}
// FilterByStatusCode return ListAudits with specified status code.
func (la ListAudits) FilterByStatusCode(statusCode uint64) ListAudits {
predicate := func(ic ItemAudit) bool {
return ic.StatusCode == statusCode
}
return la.FilterFunc(predicate)
}
// FilterFunc allows filtering ListAudits based on a user-specified predicate.
func (la ListAudits) FilterFunc(predicate func(ItemAudit) bool) ListAudits {
var result ListAudits
for _, item := range la.Data {
if predicate(item) {
result.Data = append(result.Data, item)
}
}
result.EntryCount = uint64(len(result.Data))
return result
}
// FindOne returns first found ItemAudit
// If none was found, returns an empty struct.
func (la ListAudits) FindOne() ItemAudit {
if len(la.Data) == 0 {
return ItemAudit{}
}
return la.Data[0]
}

View File

@@ -0,0 +1,115 @@
package audit
import (
"testing"
)
var audits = ListAudits{
Data: []ItemAudit{
{
Args: "[]",
Call: "/restmachine/cloudapi/audit/linkedJobs",
GUID: "550e8400-e29b-41d4-a716-446655440001",
CorrelationID: "550e8400-e29b-41d4-a716-446655440001",
Kwargs: `{\"audit_guid\":\"dd8623a1-a887-48c1-a500-c10210d404cf\"}`,
RemoteAddr: "192.168.1.100",
ResponseTime: 1,
Result: `[]`,
StatusCode: 200,
Timestamp: 1640995200,
TimestampEnd: 1640995201,
User: "test@example.com",
TTL: "2025-07-31T14:22:57.028000",
},
{
Args: "[]",
Call: "/restmachine/cloudapi/audit/test",
GUID: "550e8400-e29b-41d4-a716-446655440002",
CorrelationID: "550e8400-e29b-41d4-a716-446655440002",
Kwargs: `{\"audit_guid\":\"dd8623a1-a887-48c1-a500-c10210d404cf\"}`,
RemoteAddr: "192.168.1.105",
ResponseTime: 5,
Result: `[]`,
StatusCode: 400,
Timestamp: 1640995200,
TimestampEnd: 1640995201,
User: "test2@example.com",
TTL: "2025-07-31T14:22:57.028000",
},
},
EntryCount: 2,
}
func TestFilterByID(t *testing.T) {
actual := audits.FilterByID("550e8400-e29b-41d4-a716-446655440002").FindOne()
if actual.GUID != "550e8400-e29b-41d4-a716-446655440002" {
t.Fatal("expected GUID 550e8400-e29b-41d4-a716-446655440002, found: ", actual.GUID)
}
actualEmpty := audits.FilterByID("")
if len(actualEmpty.Data) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty.Data))
}
}
func TestFilterByCorrelationID(t *testing.T) {
actual := audits.FilterByCorrelationID("550e8400-e29b-41d4-a716-446655440002").FindOne()
if actual.CorrelationID != "550e8400-e29b-41d4-a716-446655440002" {
t.Fatal("expected GUID 550e8400-e29b-41d4-a716-446655440002, found: ", actual.CorrelationID)
}
actualEmpty := audits.FilterByCorrelationID("")
if len(actualEmpty.Data) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty.Data))
}
}
func TestFilterByRemoteAddr(t *testing.T) {
actual := audits.FilterByRemoteAddr("192.168.1.100").FindOne()
if actual.RemoteAddr != "192.168.1.100" {
t.Fatal("expected remote address 192.168.1.100, found: ", actual.RemoteAddr)
}
actualEmpty := audits.FilterByRemoteAddr("")
if len(actualEmpty.Data) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty.Data))
}
}
func TestFilterByUser(t *testing.T) {
actual := audits.FilterByUser("test@example.com").FindOne()
if actual.User != "test@example.com" {
t.Fatal("expected user test@example.com, found: ", actual.RemoteAddr)
}
actualEmpty := audits.FilterByUser("")
if len(actualEmpty.Data) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty.Data))
}
}
func TestFilterByCall(t *testing.T) {
actual := audits.FilterByCall("/restmachine/cloudapi/audit/test").FindOne()
if actual.Call != "/restmachine/cloudapi/audit/test" {
t.Fatal("expected call /restmachine/cloudapi/audit/test, found: ", actual.Call)
}
}
func TestFilterByStatusCode(t *testing.T) {
actual := audits.FilterByStatusCode(200)
for _, item := range actual.Data {
if item.StatusCode != 200 {
t.Fatal("expected 200 status code, found: ", item.StatusCode)
}
}
}

124
pkg/cloudapi/audit/list.go Normal file
View File

@@ -0,0 +1,124 @@
package audit
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// ListRequest struct to give list of account audits
type ListRequest struct {
// Find all audits after point in time (unixtime)
// Required: false
TimestampAt uint64 `url:"timestamp_at,omitempty" json:"timestamp_at,omitempty"`
// Find all audits before point in time (unixtime)
// Required: false
TimestampTo uint64 `url:"timestamp_to,omitempty" json:"timestamp_to,omitempty"`
// Find by user (Mongo RegExp supported)
// Required: false
User string `url:"user,omitempty" json:"user,omitempty"`
// Find by api endpoint (Mongo RegExp supported)
// Required: false
Call string `url:"call,omitempty" json:"call,omitempty"`
// Find by request id
// Required: false
RequestID string `url:"request_id,omitempty" json:"request_id,omitempty"`
// Find by HTTP min status code
// Required: false
MinStatusCode uint64 `url:"min_status_code,omitempty" json:"min_status_code,omitempty"`
// Find by HTTP max status code
// Required: false
MaxStatusCode uint64 `url:"max_status_code,omitempty" json:"max_status_code,omitempty"`
// Sort by one of supported fields, format +|-(field)
// Required: false
SortBy string `url:"sort_by,omitempty" json:"sort_by,omitempty" validate:"omitempty,sortBy"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
// Find by resource group id
// Required: false
RGID uint64 `url:"resgroup_id,omitempty" json:"resgroup_id,omitempty"`
// Find by compute id
// Required: false
ComputeID uint64 `url:"compute_id,omitempty" json:"compute_id,omitempty"`
// Find by account id
// Required: false
AccountID uint64 `url:"account_id,omitempty" json:"account_id,omitempty"`
// Find by vins id
// Required: false
VINSID uint64 `url:"vins_id,omitempty" json:"vins_id,omitempty"`
// Find by service id
// Required: false
ServiceID uint64 `url:"service_id,omitempty" json:"service_id,omitempty"`
// Find by k8s id
// Required: false
K8SID uint64 `url:"k8s_id,omitempty" json:"k8s_id,omitempty"`
// Find by flipgroup id
// Required: false
FLIPGroupID uint64 `url:"flipgroup_id,omitempty" json:"flipgroup_id,omitempty"`
// Find by load balancer id
// Required: false
LBID uint64 `url:"lb_id,omitempty" json:"lb_id,omitempty"`
// Find by sep id
// Required: false
SEPID uint64 `url:"sep_id,omitempty" json:"sep_id,omitempty"`
// Exclude audit lines from response
// Required: false
ExcludeAuditLines bool `url:"exclude_audit_lines,omitempty" json:"exclude_audit_lines,omitempty"`
}
// List gets audit records for the specified account object
func (a Audit) List(ctx context.Context, req ListRequest) (*ListAudits, error) {
res, err := a.ListRaw(ctx, req)
if err != nil {
return nil, err
}
list := ListAudits{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return &list, nil
}
// ListRaw gets list of audit records an array of bytes
func (a Audit) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
if err := validators.ValidateRequest(req); err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudapi/audit/list"
res, err := a.client.DecortApiCall(ctx, http.MethodPost, url, req)
return res, err
}

View File

@@ -42,3 +42,54 @@ type RecordAudit struct {
// User
User string `json:"user"`
}
// Main info about audit
type ItemAudit struct {
// Args
Args string `json:"args"`
// Call
Call string `json:"call"`
// GUID
GUID string `json:"guid"`
// Correlation ID
CorrelationID string `json:"correlation_id"`
// Kwargs
Kwargs string `json:"kwargs"`
// RemoteAddr
RemoteAddr string `json:"remote_addr"`
// Response time
ResponseTime float64 `json:"responsetime"`
// Result
Result string `json:"result"`
// Status code
StatusCode uint64 `json:"statuscode"`
// Timestamp
Timestamp float64 `json:"timestamp"`
// Timestamp End
TimestampEnd float64 `json:"timestampEnd"`
// User
User string `json:"user"`
// TTL
TTL string `json:"_ttl"`
}
// List of audits
type ListAudits struct {
// Data
Data []ItemAudit `json:"data"`
// EntryCount
EntryCount uint64 `json:"entryCount"`
}

View File

@@ -24,7 +24,7 @@ type ChangeSecGroupsRequest struct {
// Flag indicating whether security groups are enabled for this interface
// Required: false
EnableSecGroups bool `url:"enable_secgroups,omitempty" json:"enable_secgroups,omitempty"`
EnableSecGroups interface{} `url:"enable_secgroups,omitempty" json:"enable_secgroups,omitempty" validate:"omitempty,isBool"`
}
// ChangeSecGroups changes security groups for compute

View File

@@ -3,8 +3,8 @@ package compute
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
@@ -18,6 +18,10 @@ type CloneRequest struct {
// Required: true
Name string `url:"name" json:"name" validate:"required"`
// ID of the Storage Policy
// Required: true
StoragePolicyID uint64 `url:"storage_policy_id" json:"storage_policy_id" validate:"required"`
// Timestamp of the parent's snapshot to create clone from
// Required: false
SnapshotTimestamp uint64 `url:"snapshotTimestamp,omitempty" json:"snapshotTimestamp,omitempty"`
@@ -30,26 +34,29 @@ type CloneRequest struct {
// Default: false
// Required: false
Force bool `url:"force" json:"force"`
// The name of the pool to migrate disks to
// Required: false
PoolName string `url:"pool_name" json:"pool_name"`
// The ID of the SEP to migrate disks to
// Required: false
SEPID uint64 `url:"sep_id" json:"sep_id"`
}
// Clone clones compute instance
func (c Compute) Clone(ctx context.Context, req CloneRequest) (uint64, error) {
func (c Compute) Clone(ctx context.Context, req CloneRequest) (string, error) {
err := validators.ValidateRequest(req)
if err != nil {
return 0, validators.ValidationErrors(validators.GetErrors(err))
return "", validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudapi/compute/clone"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
res, err := c.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
if err != nil {
return 0, err
return "", err
}
result, err := strconv.ParseUint(string(res), 10, 64)
if err != nil {
return 0, err
}
return result, nil
return string(res), nil
}

View File

@@ -0,0 +1,39 @@
package compute
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// CloneAbortRequest struct to abort a compute clone
type CloneAbortRequest struct {
// ID of compute instance
// Required: true
ComputeID uint64 `url:"compute_id" json:"compute_id" validate:"required"`
}
// CloneAbort aborts a compute clone
func (c Compute) CloneAbort(ctx context.Context, req CloneAbortRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
return false, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudapi/compute/clone_abort"
res, err := c.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

View File

@@ -0,0 +1,40 @@
package compute
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// GetCloneStatusRequest struct to get information about compute clone status
type GetCloneStatusRequest struct {
// ID of compute instance
// Required: true
ComputeID uint64 `url:"compute_id" json:"compute_id" validate:"required"`
}
// GetCloneStatus gets information about compute clone status as a RecordCloneStatus struct
func (c Compute) GetCloneStatus(ctx context.Context, req GetCloneStatusRequest) ([]RecordCloneStatus, error) {
err := validators.ValidateRequest(req)
if err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudapi/compute/clone_status"
res, err := c.client.DecortApiCall(ctx, http.MethodGet, url, req)
if err != nil {
return nil, err
}
cloneStatus := make([]RecordCloneStatus, 0)
err = json.Unmarshal(res, &cloneStatus)
if err != nil {
return nil, err
}
return cloneStatus, nil
}

View File

@@ -3,7 +3,6 @@ package compute
import (
"context"
"net/http"
"strconv"
"strings"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
@@ -27,47 +26,15 @@ type wrapperCreateTemplateRequest struct {
}
// CreateTemplate create template from compute instance
func (c Compute) CreateTemplate(ctx context.Context, req CreateTemplateRequest) (uint64, error) {
err := validators.ValidateRequest(req)
if err != nil {
return 0, validators.ValidationErrors(validators.GetErrors(err))
}
reqWrapped := wrapperCreateTemplateRequest{
CreateTemplateRequest: req,
AsyncMode: false,
}
url := "/cloudapi/compute/createTemplate"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, reqWrapped)
if err != nil {
return 0, err
}
result, err := strconv.ParseUint(string(res), 10, 64)
if err != nil {
return 0, err
}
return result, nil
}
// CreateTemplateAsync create template from compute instance
func (c Compute) CreateTemplateAsync(ctx context.Context, req CreateTemplateRequest) (string, error) {
func (c Compute) CreateTemplate(ctx context.Context, req CreateTemplateRequest) (string, error) {
err := validators.ValidateRequest(req)
if err != nil {
return "", validators.ValidationErrors(validators.GetErrors(err))
}
reqWrapped := wrapperCreateTemplateRequest{
CreateTemplateRequest: req,
AsyncMode: true,
}
url := "/cloudapi/compute/createTemplate"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, reqWrapped)
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return "", err
}

View File

@@ -50,6 +50,14 @@ type DiskAddRequest struct {
// Specify image id for create disk from template
// Required: false
ImageID uint64 `url:"imageId,omitempty" json:"imageId,omitempty"`
// Desired PCI slot (hex string, e.g. "0x1A")
// Required: false
PCISlot string `url:"pci_slot,omitempty" json:"pci_slot,omitempty"`
// Desired bus number (hex string, e.g. "0x03")
// Required: false
BusNumber string `url:"bus_number,omitempty" json:"bus_number,omitempty"`
}
// DiskAdd creates new disk and attach to compute

View File

@@ -21,6 +21,14 @@ type DiskAttachRequest struct {
// Type of the disk B;D
// Required: false
DiskType string `url:"diskType,omitempty" json:"diskType,omitempty" validate:"omitempty,computeDiskType"`
// Desired PCI slot (hex string, e.g. "0x1A")
// Required: false
PCISlot string `url:"pci_slot,omitempty" json:"pci_slot,omitempty"`
// Desired bus number (hex string, e.g. "0x03")
// Required: false
BusNumber string `url:"bus_number,omitempty" json:"bus_number,omitempty"`
}
// DiskAttach attach disk to compute

View File

@@ -161,6 +161,9 @@ type RecordAffinityRelations struct {
// Main information about attached network
type RecordNetAttach struct {
// Bus number
BusNumber uint64 `json:"bus_number"`
// Connection ID
ConnID uint64 `json:"connId"`
@@ -173,6 +176,9 @@ type RecordNetAttach struct {
// Enabled
Enabled bool `json:"enabled"`
// Enable security groups
EnableSecGroups bool `json:"enable_secgroups"`
// FLIPGroup ID
FLIPGroupID uint64 `json:"flipgroupId"`
@@ -182,12 +188,18 @@ type RecordNetAttach struct {
// IP address
IPAddress string `json:"ipAddress"`
// Libvirt Settings
LibvirtSettings LibvirtSettings `json:"libvirtSettings"`
// Listen SSH
ListenSSH bool `json:"listenSsh"`
// MAC
MAC string `json:"mac"`
// Maximum transmission unit
MTU uint64 `json:"mtu"`
// Name
Name string `json:"name"`
@@ -200,9 +212,18 @@ type RecordNetAttach struct {
// Network type
NetType string `json:"netType"`
// Node id
NodeID int `json:"nodeId"`
// PCI slot
PCISlot int64 `json:"pciSlot"`
// SDN interface ID
SDNInterfaceID string `json:"sdn_interface_id"`
// List of security groups
SecurityGroups []uint64 `json:"security_groups"`
// QOS
QOS QOS `json:"qos"`
@@ -212,11 +233,11 @@ type RecordNetAttach struct {
// Type
Type string `json:"type"`
// List of trunk tags
TrunkTags []uint64 `json:"trunk_tags"`
// List VNF IDs
VNFs []uint64 `json:"vnfs"`
// Maximum transmission unit
MTU uint64 `json:"mtu"`
}
// Detailed information about audit
@@ -311,6 +332,9 @@ type RecordCompute struct {
// Architecture
Architecture string `json:"arch"`
// Boot image ID
BootImageID uint64 `json:"boot_image_id"`
// Boot order
BootOrder []string `json:"bootOrder"`
@@ -392,6 +416,9 @@ type RecordCompute struct {
// List interfaces
Interfaces ListInterfaces `json:"interfaces"`
// Loader meta iso information
LoaderMetaIso LoaderMetaIso `json:"loaderMetaIso"`
// Live migration job ID
LiveMigrationJobID uint64 `json:"live_migration_job_id"`
@@ -519,6 +546,13 @@ type RecordCompute struct {
// Required: false
ZoneID uint64 `json:"zoneId"`
}
type LoaderMetaIso struct {
// Name
DeviceName string `json:"devicename"`
// Path
Path string `json:"path"`
}
type QemuQuest struct {
Enabled bool `json:"enabled"`
@@ -758,9 +792,18 @@ type ItemComputeDisk struct {
// Bus number
BusNumber uint64 `json:"bus_number"`
// Created by
CreatedBy string `json:"createdBy"`
// Created time
CreatedTime uint64 `json:"createdTime"`
// Device name
DeviceName string `json:"devicename"`
// Deleted by
DeletedBy string `json:"deletedBy"`
// Deleted time
DeletedTime uint64 `json:"deletedTime"`
@@ -871,6 +914,9 @@ type ItemComputeDisk struct {
// Updated by
UpdatedBy string `json:"updatedBy"`
// UpdatedTime
UpdatedTime uint64 `json:"updatedTime"`
}
type ItemReplication struct {
@@ -991,6 +1037,9 @@ type ItemCompute struct {
// Architecture
Architecture string `json:"arch"`
// Boot image ID
BootImageID uint64 `json:"boot_image_id"`
// Boot order
BootOrder []string `json:"bootOrder"`
@@ -1300,3 +1349,31 @@ type ListPCIDevices struct {
// Entry count
EntryCount uint64 `json:"entryCount"`
}
type RecordCloneStatus struct {
// Disk ID
DiskID int `json:"disk_id"`
// Clone Status
Status CloneStatus `json:"status"`
}
type CloneStatus struct {
// Type
Type int `json:"type"`
// Copy speed
Bandwidth int `json:"bandwidth"`
// Current progress
Cur int `json:"cur"`
// Total size
End int `json:"end"`
// Operation status
Ready bool `json:"ready"`
// Progress percent
ProgressPercent int `json:"progress_percent"`
}

View File

@@ -30,10 +30,6 @@ type CreateRequest struct {
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
// Max IOPS disk can perform defaults to 2000
// Required: false
IOPS uint64 `url:"iops,omitempty" json:"iops,omitempty"`
// Storage endpoint provider ID to create disk
// Required: false
SEPID uint64 `url:"sep_id,omitempty" json:"sep_id,omitempty"`

View File

@@ -69,6 +69,10 @@ type ListRequest struct {
// Find by enabled True or False
// Required: false
Enabled interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
// Find by storage policy id
// Required: false
StoragePolicyID uint64 `url:"storage_policy_id,omitempty" json:"storage_policy_id,omitempty"`
}
// List gets list of available images as a ListImages struct, optionally filtering by account ID

View File

@@ -50,6 +50,9 @@ type ItemImage struct {
// Status
Status string `json:"status"`
// Storage policy ID
StoragePolicyID uint64 `json:"storage_policy_id"`
// Type
Type string `json:"type"`
@@ -197,6 +200,9 @@ type RecordImage struct {
// Tech status
TechStatus string `json:"techStatus"`
// Need to clean before destroy
ToClean bool `json:"to_clean"`
// Type
Type string `json:"type"`

View File

@@ -0,0 +1,46 @@
package rg
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// AddStoragePolicyRequest struct for adding storage policy to the resource group
type AddStoragePolicyRequest struct {
// ID of resource group to add to
// Required: true
RGID uint64 `url:"resgroup_id" json:"resgroup_id" validate:"required"`
// ID of the storage policy to which to connect resource group
// Required: true
StoragePolicyID uint64 `url:"storage_policy_id" json:"storage_policy_id" validate:"required"`
// Limit storage resources GB. Or -1 unlimit
// Required: false
Limit int `url:"limit,omitempty" json:"limit,omitempty"`
}
// AddStoragePolicy add storage policy to the account.
func (r RG) AddStoragePolicy(ctx context.Context, req AddStoragePolicyRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
return false, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudapi/rg/add_storage_policy"
res, err := r.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

View File

@@ -0,0 +1,42 @@
package rg
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// DelStoragePolicyRequest struct for deleting storage policy to the resource group
type DelStoragePolicyRequest struct {
// ID of resource group
// Required: true
RGID uint64 `url:"resgroup_id" json:"resgroup_id" validate:"required"`
// ID of the storage policy to which to disconnect account
// Required: true
StoragePolicyID uint64 `url:"storage_policy_id" json:"storage_policy_id" validate:"required"`
}
// DelStoragePolicy delete storage policy to the account.
func (r RG) DelStoragePolicy(ctx context.Context, req DelStoragePolicyRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
return false, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudapi/rg/del_storage_policy"
res, err := r.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

View File

@@ -9,10 +9,6 @@ import (
)
type ListRequest struct {
// ID of account ID
// Required: true
AccountID uint64 `url:"account_id" json:"account_id" validate:"required"`
// Page number
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
@@ -45,6 +41,10 @@ type ListRequest struct {
// Required: false
SortBy string `url:"sort_by,omitempty" json:"sort_by,omitempty"`
// ID of account ID
// Required: false
AccountID uint64 `url:"account_id,omitempty" json:"account_id,omitempty"`
// Search by resgroup id
// Required: false
ResgroupID uint64 `url:"resgroup_id,omitempty" json:"resgroup_id,omitempty"`

View File

@@ -30,6 +30,9 @@ type ItemStoragePolicy struct {
// Max IOPS for the sotrage policy
LimitIOPS uint64 `json:"limit_iops"`
// Storage policy ID
StoragePolicyID uint64 `json:"storage_policy_id"`
// Which accounts and resource groups use the storage policy
Usage Usage `json:"usage"`
}
@@ -56,6 +59,9 @@ type InfoStoragePolicy struct {
// Max IOPS for the sotrage policy
LimitIOPS uint64 `json:"limit_iops"`
// ID of the storage policy
StoragePolicyID uint64 `json:"storage_policy_id"`
// Which accounts and resource groups use the storage policy
Usage Usage `json:"usage"`
}

View File

@@ -211,6 +211,12 @@ type RecordVNFDev struct {
// VNC password
VNCPassword string `json:"vncPasswd"`
// Zone ID
ZoneID uint64 `json:"zoneId"`
// Live migration job ID
LiveMigrationJobID uint64 `json:"live_migration_job_id"`
}
// VNF config
@@ -269,6 +275,9 @@ type ItemVNFInterface struct {
// Enabled
Enabled bool `json:"enabled"`
// Enable security groups
EnableSecGroups bool `json:"enable_secgroups"`
// FLIPGroup ID
FLIPGroupID uint64 `json:"flipgroupId"`
@@ -305,6 +314,9 @@ type ItemVNFInterface struct {
// NodeID
NodeID int64 `json:"nodeId"`
// List of security groups
SecGroups []uint64 `json:"security_groups"`
// SDNInterfaceID
SDNInterfaceID string `json:"sdn_interface_id"`
@@ -418,6 +430,9 @@ type RecordNAT struct {
// Type
Type string `json:"type"`
// Zone ID
ZoneID uint64 `json:"zoneId"`
}
// NAT configuration
@@ -486,6 +501,9 @@ type RecordGW struct {
// Type
Type string `json:"type"`
// Zone ID
ZoneID uint64 `json:"zoneId"`
}
// GW configuration
@@ -576,6 +594,9 @@ type RecordDHCP struct {
// Type
Type string `json:"type"`
// Zone ID
ZoneID uint64 `json:"zoneId"`
}
// DHCP configuration

View File

@@ -0,0 +1,81 @@
package audit
// FilterByID returns ListAudits with specified ID.
func (la ListAudits) FilterByID(guid string) ListAudits {
predicate := func(ia ItemAudit) bool {
return ia.GUID == guid
}
return la.FilterFunc(predicate)
}
// FilterByCall returns ListAudits with specified call.
func (la ListAudits) FilterByCall(call string) ListAudits {
predicate := func(ic ItemAudit) bool {
return ic.Call == call
}
return la.FilterFunc(predicate)
}
// FilterByCorrelationID returns ListAudits with specified correlation id.
func (la ListAudits) FilterByCorrelationID(correlationID string) ListAudits {
predicate := func(ic ItemAudit) bool {
return ic.CorrelationID == correlationID
}
return la.FilterFunc(predicate)
}
// FilterByRemoteAddr returns ListAudits with specified remote address.
func (la ListAudits) FilterByRemoteAddr(remoteAddr string) ListAudits {
predicate := func(ic ItemAudit) bool {
return ic.RemoteAddr == remoteAddr
}
return la.FilterFunc(predicate)
}
// FilterByUser returns ListAudits with specified user name.
func (la ListAudits) FilterByUser(user string) ListAudits {
predicate := func(ic ItemAudit) bool {
return ic.User == user
}
return la.FilterFunc(predicate)
}
// FilterByStatusCode return ListAudits with specified status code.
func (la ListAudits) FilterByStatusCode(statusCode uint64) ListAudits {
predicate := func(ic ItemAudit) bool {
return ic.StatusCode == statusCode
}
return la.FilterFunc(predicate)
}
// FilterFunc allows filtering ListAudits based on a user-specified predicate.
func (la ListAudits) FilterFunc(predicate func(ItemAudit) bool) ListAudits {
var result ListAudits
for _, item := range la.Data {
if predicate(item) {
result.Data = append(result.Data, item)
}
}
result.EntryCount = uint64(len(result.Data))
return result
}
// FindOne returns first found ItemAudit
// If none was found, returns an empty struct.
func (la ListAudits) FindOne() ItemAudit {
if len(la.Data) == 0 {
return ItemAudit{}
}
return la.Data[0]
}

View File

@@ -0,0 +1,115 @@
package audit
import (
"testing"
)
var audits = ListAudits{
Data: []ItemAudit{
{
Args: "[]",
Call: "/restmachine/cloudapi/audit/linkedJobs",
GUID: "550e8400-e29b-41d4-a716-446655440001",
CorrelationID: "550e8400-e29b-41d4-a716-446655440001",
Kwargs: `{\"audit_guid\":\"dd8623a1-a887-48c1-a500-c10210d404cf\"}`,
RemoteAddr: "192.168.1.100",
ResponseTime: 1,
Result: `[]`,
StatusCode: 200,
Timestamp: 1640995200,
TimestampEnd: 1640995201,
User: "test@example.com",
TTL: "2025-07-31T14:22:57.028000",
},
{
Args: "[]",
Call: "/restmachine/cloudapi/audit/test",
GUID: "550e8400-e29b-41d4-a716-446655440002",
CorrelationID: "550e8400-e29b-41d4-a716-446655440002",
Kwargs: `{\"audit_guid\":\"dd8623a1-a887-48c1-a500-c10210d404cf\"}`,
RemoteAddr: "192.168.1.105",
ResponseTime: 5,
Result: `[]`,
StatusCode: 400,
Timestamp: 1640995200,
TimestampEnd: 1640995201,
User: "test2@example.com",
TTL: "2025-07-31T14:22:57.028000",
},
},
EntryCount: 2,
}
func TestFilterByID(t *testing.T) {
actual := audits.FilterByID("550e8400-e29b-41d4-a716-446655440002").FindOne()
if actual.GUID != "550e8400-e29b-41d4-a716-446655440002" {
t.Fatal("expected GUID 550e8400-e29b-41d4-a716-446655440002, found: ", actual.GUID)
}
actualEmpty := audits.FilterByID("")
if len(actualEmpty.Data) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty.Data))
}
}
func TestFilterByCorrelationID(t *testing.T) {
actual := audits.FilterByCorrelationID("550e8400-e29b-41d4-a716-446655440002").FindOne()
if actual.CorrelationID != "550e8400-e29b-41d4-a716-446655440002" {
t.Fatal("expected GUID 550e8400-e29b-41d4-a716-446655440002, found: ", actual.CorrelationID)
}
actualEmpty := audits.FilterByCorrelationID("")
if len(actualEmpty.Data) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty.Data))
}
}
func TestFilterByRemoteAddr(t *testing.T) {
actual := audits.FilterByRemoteAddr("192.168.1.100").FindOne()
if actual.RemoteAddr != "192.168.1.100" {
t.Fatal("expected remote address 192.168.1.100, found: ", actual.RemoteAddr)
}
actualEmpty := audits.FilterByRemoteAddr("")
if len(actualEmpty.Data) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty.Data))
}
}
func TestFilterByUser(t *testing.T) {
actual := audits.FilterByUser("test@example.com").FindOne()
if actual.User != "test@example.com" {
t.Fatal("expected user test@example.com, found: ", actual.RemoteAddr)
}
actualEmpty := audits.FilterByUser("")
if len(actualEmpty.Data) != 0 {
t.Fatal("expected empty, actual: ", len(actualEmpty.Data))
}
}
func TestFilterByCall(t *testing.T) {
actual := audits.FilterByCall("/restmachine/cloudapi/audit/test").FindOne()
if actual.Call != "/restmachine/cloudapi/audit/test" {
t.Fatal("expected call /restmachine/cloudapi/audit/test, found: ", actual.Call)
}
}
func TestFilterByStatusCode(t *testing.T) {
actual := audits.FilterByStatusCode(200)
for _, item := range actual.Data {
if item.StatusCode != 200 {
t.Fatal("expected 200 status code, found: ", item.StatusCode)
}
}
}

View File

@@ -50,6 +50,50 @@ type ListRequest struct {
// Page size
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
// Find by resource group id
// Required: false
RGID uint64 `url:"resgroup_id,omitempty" json:"resgroup_id,omitempty"`
// Find by compute id
// Required: false
ComputeID uint64 `url:"compute_id,omitempty" json:"compute_id,omitempty"`
// Find by account id
// Required: false
AccountID uint64 `url:"account_id,omitempty" json:"account_id,omitempty"`
// Find by vins id
// Required: false
VINSID uint64 `url:"vins_id,omitempty" json:"vins_id,omitempty"`
// Find by service id
// Required: false
ServiceID uint64 `url:"service_id,omitempty" json:"service_id,omitempty"`
// Find by k8s id
// Required: false
K8SID uint64 `url:"k8s_id,omitempty" json:"k8s_id,omitempty"`
// Find by flipgroup id
// Required: false
FLIPGroupID uint64 `url:"flipgroup_id,omitempty" json:"flipgroup_id,omitempty"`
// Find by load balancer id
// Required: false
LBID uint64 `url:"lb_id,omitempty" json:"lb_id,omitempty"`
// Find by sep id
// Required: false
SEPID uint64 `url:"sep_id,omitempty" json:"sep_id,omitempty"`
// Find by node id
// Required: false
NodeID uint64 `url:"node_id,omitempty" json:"node_id,omitempty"`
// Exclude audit lines from response
// Required: false
ExcludeAuditLines bool `url:"exclude_audit_lines,omitempty" json:"exclude_audit_lines,omitempty"`
}
// List gets audit records for the specified account object

View File

@@ -40,9 +40,6 @@ type ItemAudit struct {
// TTL
TTL string `json:"_ttl"`
// Tags
Tags string `json:"tags"`
}
// List of audits

View File

@@ -24,7 +24,7 @@ type ChangeSecGroupsRequest struct {
// Flag indicating whether security groups are enabled for this interface
// Required: false
EnableSecGroups bool `url:"enable_secgroups,omitempty" json:"enable_secgroups,omitempty"`
EnableSecGroups interface{} `url:"enable_secgroups,omitempty" json:"enable_secgroups,omitempty" validate:"omitempty,isBool"`
}
// ChangeSecGroups changes security groups for compute

View File

@@ -3,8 +3,8 @@ package compute
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
@@ -18,6 +18,10 @@ type CloneRequest struct {
// Required: true
Name string `url:"name" json:"name" validate:"required"`
// ID of the Storage Policy
// Required: true
StoragePolicyID uint64 `url:"storage_policy_id" json:"storage_policy_id" validate:"required"`
// Timestamp of the parent's snapshot to create clone from
// Required: false
SnapshotTimestamp uint64 `url:"snapshotTimestamp" json:"snapshotTimestamp"`
@@ -30,26 +34,29 @@ type CloneRequest struct {
// Default: false
// Required: false
Force bool `url:"force" json:"force"`
// The name of the pool to migrate disks to
// Required: false
PoolName string `url:"pool_name" json:"pool_name"`
// The ID of the SEP to migrate disks to
// Required: false
SEPID uint64 `url:"sep_id" json:"sep_id"`
}
// Clone clones compute instance
func (c Compute) Clone(ctx context.Context, req CloneRequest) (uint64, error) {
func (c Compute) Clone(ctx context.Context, req CloneRequest) (string, error) {
err := validators.ValidateRequest(req)
if err != nil {
return 0, validators.ValidationErrors(validators.GetErrors(err))
return "", validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudbroker/compute/clone"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
res, err := c.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
if err != nil {
return 0, err
return "", err
}
result, err := strconv.ParseUint(string(res), 10, 64)
if err != nil {
return 0, err
}
return result, nil
return string(res), nil
}

View File

@@ -0,0 +1,39 @@
package compute
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// CloneAbortRequest struct to abort a compute clone
type CloneAbortRequest struct {
// ID of compute instance
// Required: true
ComputeID uint64 `url:"compute_id" json:"compute_id" validate:"required"`
}
// CloneAbort aborts a compute clone
func (c Compute) CloneAbort(ctx context.Context, req CloneAbortRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
return false, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudbroker/compute/clone_abort"
res, err := c.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

View File

@@ -0,0 +1,40 @@
package compute
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// GetCloneStatusRequest struct to get information about compute clone status
type GetCloneStatusRequest struct {
// ID of compute instance
// Required: true
ComputeID uint64 `url:"compute_id" json:"compute_id" validate:"required"`
}
// GetCloneStatus gets information about compute clone status as a RecordCloneStatus struct
func (c Compute) GetCloneStatus(ctx context.Context, req GetCloneStatusRequest) ([]RecordCloneStatus, error) {
err := validators.ValidateRequest(req)
if err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudbroker/compute/clone_status"
res, err := c.client.DecortApiCall(ctx, http.MethodGet, url, req)
if err != nil {
return nil, err
}
cloneStatus := make([]RecordCloneStatus, 0)
err = json.Unmarshal(res, &cloneStatus)
if err != nil {
return nil, err
}
return cloneStatus, nil
}

View File

@@ -3,7 +3,6 @@ package compute
import (
"context"
"net/http"
"strconv"
"strings"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
@@ -20,27 +19,16 @@ type CreateTemplateRequest struct {
Name string `url:"name" json:"name" validate:"required"`
}
type wrapperCreateTemplateRequest struct {
CreateTemplateRequest
AsyncMode bool `url:"asyncMode"`
}
// CreateTemplateAsync create template from compute instance
func (c Compute) CreateTemplateAsync(ctx context.Context, req CreateTemplateRequest) (string, error) {
// CreateTemplate create template from compute instance
func (c Compute) CreateTemplate(ctx context.Context, req CreateTemplateRequest) (string, error) {
err := validators.ValidateRequest(req)
if err != nil {
return "", validators.ValidationErrors(validators.GetErrors(err))
}
reqWrapped := wrapperCreateTemplateRequest{
CreateTemplateRequest: req,
AsyncMode: true,
}
url := "/cloudbroker/compute/createTemplate"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, reqWrapped)
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return "", err
}
@@ -49,30 +37,3 @@ func (c Compute) CreateTemplateAsync(ctx context.Context, req CreateTemplateRequ
return result, nil
}
// CreateTemplate create template from compute instance
func (c Compute) CreateTemplate(ctx context.Context, req CreateTemplateRequest) (uint64, error) {
err := validators.ValidateRequest(req)
if err != nil {
return 0, validators.ValidationErrors(validators.GetErrors(err))
}
reqWrapped := wrapperCreateTemplateRequest{
CreateTemplateRequest: req,
AsyncMode: false,
}
url := "/cloudbroker/compute/createTemplate"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, reqWrapped)
if err != nil {
return 0, err
}
result, err := strconv.ParseUint(string(res), 10, 64)
if err != nil {
return 0, err
}
return result, nil
}

View File

@@ -50,6 +50,14 @@ type DiskAddRequest struct {
// Specify image id for create disk from template
// Required: false
ImageID uint64 `url:"imageId,omitempty" json:"imageId,omitempty"`
// Desired PCI slot (hex string, e.g. "0x1A")
// Required: false
PCISlot string `url:"pci_slot,omitempty" json:"pci_slot,omitempty"`
// Desired bus number (hex string, e.g. "0x03")
// Required: false
BusNumber string `url:"bus_number,omitempty" json:"bus_number,omitempty"`
}
// DiskAdd creates new disk and attach to compute

View File

@@ -21,6 +21,14 @@ type DiskAttachRequest struct {
// Type of the disk B;D
// Required: false
DiskType string `url:"diskType,omitempty" json:"diskType,omitempty"`
// Desired PCI slot (hex string, e.g. "0x1A")
// Required: false
PCISlot string `url:"pci_slot,omitempty" json:"pci_slot,omitempty"`
// Desired bus number (hex string, e.g. "0x03")
// Required: false
BusNumber string `url:"bus_number,omitempty" json:"bus_number,omitempty"`
}
// DiskAttach attach disk to compute

View File

@@ -20,7 +20,7 @@ type MigrateStorageListRequest struct {
// Sort by one of supported fields, format +|-(field)
// Required: false
SortBy string `url:"sortBy,omitempty" json:"sortBy,omitempty" validate:"omitempty,sortBy"`
SortBy string `url:"sort_by,omitempty" json:"sort_by,omitempty" validate:"omitempty,sortBy"`
// Page number
// Required: false

View File

@@ -127,6 +127,9 @@ type QOS struct {
// Main information about attached network
type RecordNetAttach struct {
// Bus number
BusNumber uint64 `json:"bus_number"`
// Connection ID
ConnID uint64 `json:"connId"`
@@ -139,6 +142,9 @@ type RecordNetAttach struct {
// Enabled
Enabled bool `json:"enabled"`
// Enable security groups
EnableSecGroups bool `json:"enable_secgroups"`
// FLIPGroup ID
FLIPGroupID uint64 `json:"flipgroupId"`
@@ -148,12 +154,18 @@ type RecordNetAttach struct {
// IP address
IPAddress string `json:"ipAddress"`
// Libvirt Settings
LibvirtSettings LibvirtSettings `json:"libvirtSettings"`
// Listen SSH
ListenSSH bool `json:"listenSsh"`
// MAC
MAC string `json:"mac"`
// Maximum transmission unit
MTU uint64 `json:"mtu"`
// Name
Name string `json:"name"`
@@ -166,9 +178,18 @@ type RecordNetAttach struct {
// Network type
NetType string `json:"netType"`
// Node id
NodeID int `json:"nodeId"`
// PCI slot
PCISlot int64 `json:"pciSlot"`
// SDN interface ID
SDNInterfaceID string `json:"sdn_interface_id"`
// List of security groups
SecurityGroups []uint64 `json:"security_groups"`
// QOS
QOS QOS `json:"qos"`
@@ -178,6 +199,9 @@ type RecordNetAttach struct {
// Type
Type string `json:"type"`
// List of trunk tags
TrunkTags []uint64 `json:"trunk_tags"`
// List VNF IDs
VNFs []uint64 `json:"vnfs"`
}
@@ -373,9 +397,18 @@ type ItemDisk struct {
// Bus number
BusNumber uint64 `json:"bus_number"`
// Created by
CreatedBy string `json:"createdBy"`
// Created time
CreatedTime uint64 `json:"createdTime"`
// Device name
DeviceName string `json:"devicename"`
// Deleted by
DeletedBy string `json:"deletedBy"`
// Deleted time
DeletedTime uint64 `json:"deletedTime"`
@@ -495,6 +528,9 @@ type ItemDisk struct {
// Updated by
UpdatedBy string `json:"updatedBy"`
// UpdatedTime
UpdatedTime uint64 `json:"updatedTime"`
}
type ItemReplication struct {
@@ -651,6 +687,9 @@ type InfoCompute struct {
// Architecture
Arch string `json:"arch"`
// Boot image ID
BootImageID uint64 `json:"boot_image_id"`
// Boot order
BootOrder []string `json:"bootOrder"`
@@ -905,6 +944,9 @@ type RecordCompute struct {
// Architecture
Arch string `json:"arch"`
// Boot image ID
BootImageID uint64 `json:"boot_image_id"`
// Boot order
BootOrder []string `json:"bootOrder"`
@@ -980,12 +1022,15 @@ type RecordCompute struct {
// Image ID
ImageID uint64 `json:"imageId"`
// ImageName
// Image name
ImageName string `json:"imageName"`
// List interfaces
Interfaces ListInterfaces `json:"interfaces"`
// Loader meta iso information
LoaderMetaIso LoaderMetaIso `json:"loaderMetaIso"`
// Live migration job ID
LiveMigrationJobID uint64 `json:"live_migration_job_id"`
@@ -1125,6 +1170,14 @@ type RecordCompute struct {
ZoneID uint64 `json:"zoneId"`
}
type LoaderMetaIso struct {
// Name
DeviceName string `json:"devicename"`
// Path
Path string `json:"path"`
}
type VGPUItem struct {
// ID
ID uint64 `json:"id"`
@@ -1397,3 +1450,31 @@ type MigrateStorageItem struct {
// Target stack ID
TargetStackID uint64 `json:"targetStackId"`
}
type RecordCloneStatus struct {
// Disk ID
DiskID int `json:"disk_id"`
// Clone Status
Status CloneStatus `json:"status"`
}
type CloneStatus struct {
// Type
Type int `json:"type"`
// Copy speed
Bandwidth int `json:"bandwidth"`
// Current progress
Cur int `json:"cur"`
// Total size
End int `json:"end"`
// Operation status
Ready bool `json:"ready"`
// Progress percent
ProgressPercent int `json:"progress_percent"`
}

View File

@@ -0,0 +1,58 @@
package compute
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// StartMigrationOutRequest struct to start compute for external migration out
type StartMigrationOutRequest struct {
// ID of compute instance
// Required: true
ComputeID uint64 `url:"compute_id" json:"compute_id" validate:"required"`
// Remote libvirt host to connect to
// Required: true
Target string `url:"target" json:"target" validate:"required"`
// Graphics handling on the destination
// Required: true
Graphics string `url:"graphics" json:"graphics" validate:"required"`
// Optional new domain name on the destination
// Required: false
NewName string `url:"new_name,omitempty" json:"new_name,omitempty"`
// When true, adds libvirt's UNSAFE flag to force migration even if libvirt marks it unsafe.
// Default: false
// Required: false
UseUnsafe bool `url:"use_unsafe,omitempty" json:"use_unsafe,omitempty"`
// Mapping of guest disk target names to absolute paths on the destination host.
// Required: false
Diskmap map[string]string `url:"diskmap,omitempty" json:"diskmap,omitempty"`
// Mapping for CD/DVD devices or their source paths to new ISO/device paths on the destination
// Required: false
CDROMMap map[string]string `url:"cdrommap,omitempty" json:"cdrommap,omitempty"`
}
// StartMigrationOut starts compute for external migration out
func (c Compute) StartMigrationOut(ctx context.Context, req StartMigrationOutRequest) (string, error) {
err := validators.ValidateRequest(req)
if err != nil {
return "", validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudbroker/compute/start_migration_out"
res, err := c.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
if err != nil {
return "", err
}
return string(res), nil
}

View File

@@ -4,9 +4,26 @@ import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// OSUsers struct contains OS user data for Guest OS.
// Must be provided if NewVMUUID is provided.
type OSUser struct {
// Login of a user
Login string `url:"login,omitempty" json:"login,omitempty"`
// Password of a user
Password string `url:"password,omitempty" json:"password,omitempty"`
// GUID
GUID string `url:"guid,omitempty" json:"guid,omitempty"`
// Pubkey
Pubkey string `url:"pubkey,omitempty" json:"pubkey,omitempty"`
}
// StopMigrationINRequest struct to stop compute for external migration in
type StopMigrationINRequest struct {
// ID of compute instance
@@ -16,10 +33,14 @@ type StopMigrationINRequest struct {
// If provided, indicates the UUID of the VM on the target host and that migration completed.
// Required: false
NewVMUUID string `url:"new_vm_uuid,omitempty" json:"new_vm_uuid,omitempty"`
// OS user data for Guest OS
// Required: false
OSUsers []OSUser `url:"os_users,omitempty" json:"os_users,omitempty"`
}
// StopMigrationIN stops compute for external migration in
func (c Compute) StopMigrationIN(ctx context.Context, req StartRequest) (string, error) {
func (c Compute) StopMigrationIN(ctx context.Context, req StopMigrationINRequest) (string, error) {
err := validators.ValidateRequest(req)
if err != nil {
return "", validators.ValidationErrors(validators.GetErrors(err))
@@ -27,7 +48,7 @@ func (c Compute) StopMigrationIN(ctx context.Context, req StartRequest) (string,
url := "/cloudbroker/compute/stop_migration_in"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
res, err := c.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
if err != nil {
return "", err
}

View File

@@ -0,0 +1,37 @@
package compute
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// StopMigrationOutRequest struct to stop compute for external migration out
type StopMigrationOutRequest struct {
// ID of compute instance
// Required: true
ComputeID uint64 `url:"computeId" json:"computeId" validate:"required"`
// Identifier of the agent-side external migration (LibvirtMigrateToURI3Request) job
// Required: false
MigrateToURI3JobID string `url:"migrate_to_uri3_job_id,omitempty" json:"migrate_to_uri3_job_id,omitempty"`
}
// StopMigrationOut stops compute for external migration out
func (c Compute) StopMigrationOut(ctx context.Context, req StopMigrationOutRequest) (string, error) {
err := validators.ValidateRequest(req)
if err != nil {
return "", validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudbroker/compute/stop_migration_out"
res, err := c.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
if err != nil {
return "", err
}
return string(res), nil
}

View File

@@ -30,10 +30,6 @@ type CreateRequest struct {
// Required: false
Size uint64 `url:"size,omitempty" json:"size,omitempty"`
// Max IOPS disk can perform defaults to 2000
// Required: false
IOPS uint64 `url:"iops,omitempty" json:"iops,omitempty"`
// Storage endpoint provider ID to create disk
// Required: false
SEPID uint64 `url:"sep_id,omitempty" json:"sep_id,omitempty"`

View File

@@ -0,0 +1,46 @@
package disks
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// Migrate struct to move disk to another sep, pool and storage policy
type MigrateRequest struct {
// ID of the disk
// Required: true
DiskID uint64 `url:"disk_id" json:"disk_id" validate:"required"`
// ID of the new SEP
// Required: true
SEPID uint64 `url:"sep_id" json:"sep_id" validate:"required"`
// New pool name
// Required: true
PoolName string `url:"pool_name" json:"pool_name" validate:"required"`
// ID if the storage policy
// Required: false
StoragePolicyID uint64 `url:"storage_policy_id,omitempty" json:"storage_policy_id,omitempty"`
}
// Move moves disk to another sep, pool and storage policy
func (c Disks) Migrate(ctx context.Context, req MigrateRequest) (string, error) {
err := validators.ValidateRequest(req)
if err != nil {
return "", validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudbroker/disks/migrate"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return "", err
}
result := string(res)
return result, nil
}

View File

@@ -0,0 +1,38 @@
package disks
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// MigrateAbortRequest struct to abort migration
type MigrateAbortRequest struct {
// ID of the disk
// Required: true
DiskID uint64 `url:"disk_id" json:"disk_id" validate:"required"`
}
// MigrateAbort aborts disk migration
func (c Disks) MigrateAbort(ctx context.Context, req MigrateAbortRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
return false, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudbroker/disks/migrate_abort"
res, err := c.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

View File

@@ -0,0 +1,40 @@
package disks
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// GetMigrateStatusRequest struct to get information about disk migrate status
type GetMigrateStatusRequest struct {
// ID of disk
// Required: true
DiskID uint64 `url:"disk_id" json:"disk_id" validate:"required"`
}
// GetMigrateStatus gets information about disk migrate status
func (c Disks) GetMigrateStatus(ctx context.Context, req GetMigrateStatusRequest) (*MigrateStatus, error) {
err := validators.ValidateRequest(req)
if err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudbroker/disks/migrate_status"
res, err := c.client.DecortApiCall(ctx, http.MethodGet, url, req)
if err != nil {
return nil, err
}
status := MigrateStatus{}
err = json.Unmarshal(res, &status)
if err != nil {
return nil, err
}
return &status, nil
}

View File

@@ -320,3 +320,20 @@ type ListTypes struct {
// Entry count
EntryCount uint64 `json:"entryCount"`
}
type MigrateStatus struct {
// Type
Type int `json:"type"`
// Copy speed
Bandwidth int `json:"bandwidth"`
// Current progress
Cur interface{} `json:"cur"`
// Total size
End interface{} `json:"end"`
// Progress percent
ProgressPercent int `json:"progress_percent"`
}

View File

@@ -2,6 +2,7 @@ package image
import (
"context"
"encoding/json"
"net/http"
"strconv"
@@ -43,6 +44,11 @@ type CreateCDROMImageRequest struct {
PasswordDl string `url:"passwordDL,omitempty" json:"passwordDL,omitempty"`
}
type asyncWrapperCreateCDROMImageRequest struct {
CreateCDROMImageRequest
AsyncMode bool `url:"asyncMode"`
}
// CreateCDROMImage creates CD-ROM image from an ISO identified by URL
func (i Image) CreateCDROMImage(ctx context.Context, req CreateCDROMImageRequest) (uint64, error) {
err := validators.ValidateRequest(req)
@@ -52,7 +58,9 @@ func (i Image) CreateCDROMImage(ctx context.Context, req CreateCDROMImageRequest
url := "/cloudbroker/image/createCDROMImage"
res, err := i.client.DecortApiCall(ctx, http.MethodPost, url, req)
syncReq := asyncWrapperCreateCDROMImageRequest{CreateCDROMImageRequest: req, AsyncMode: false}
res, err := i.client.DecortApiCall(ctx, http.MethodPost, url, syncReq)
if err != nil {
return 0, err
}
@@ -64,3 +72,29 @@ func (i Image) CreateCDROMImage(ctx context.Context, req CreateCDROMImageRequest
return result, nil
}
// AsyncCreateCDROMImage creates CD-ROM image from an ISO identified by URL in async mode
func (i Image) AsyncCreateCDROMImage(ctx context.Context, req CreateCDROMImageRequest) (string, error) {
err := validators.ValidateRequest(req)
if err != nil {
return "", validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudbroker/image/createCDROMImage"
asyncReq := asyncWrapperCreateCDROMImageRequest{CreateCDROMImageRequest: req, AsyncMode: true}
res, err := i.client.DecortApiCall(ctx, http.MethodPost, url, asyncReq)
if err != nil {
return " ", err
}
var taskID string
err = json.Unmarshal(res, &taskID)
if err != nil {
return "", err
}
return taskID, nil
}

View File

@@ -11,7 +11,7 @@ import (
// CreateRequest struct to create image
type CreateRequest struct {
// Name of the rescue disk
// Name of the image
// Required: true
Name string `url:"name" json:"name" validate:"required"`

View File

@@ -69,6 +69,10 @@ type ListRequest struct {
// Find by enabled True or False
// Required: false
Enabled interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
// Find by storage policy id
// Required: false
StoragePolicyID uint64 `url:"storage_policy_id,omitempty" json:"storage_policy_id,omitempty"`
}
// List gets list of information about images as a ListImages struct

View File

@@ -116,6 +116,9 @@ type RecordImage struct {
// Tech status
TechStatus string `json:"techStatus"`
// Need to clean before destroy
ToClean bool `json:"to_clean"`
// Type
Type string `json:"type"`
@@ -245,6 +248,9 @@ type ItemImage struct {
// Tech status
TechStatus string `json:"techStatus"`
// Need to clean before destroy
ToClean bool `json:"to_clean"`
// Type
Type string `json:"type"`

View File

@@ -21,9 +21,7 @@ type Interface struct {
// - TRUNK
NetType string `url:"netType" json:"netType" validate:"required,kvmx86NetType"`
// Network ID for connect to,
// for EXTNET - external network ID,
// for VINS - VINS ID,
// Network ID for connect
NetID uint64 `url:"netId" json:"netId" validate:"required"`
// IP address to assign to this VM when connecting to the specified network

View File

@@ -8,6 +8,47 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
type InterfaceMassCreate struct {
// Network type
// Should be one of:
// - VINS
// - EXTNET
// - TRUNK
NetType string `url:"netType" json:"netType" validate:"required,massCreateNetType"`
// Network ID for connect
NetID uint64 `url:"netId" json:"netId" validate:"required"`
// IP address to assign to this VM when connecting to the specified network
// Required: false
IPAddr string `url:"ipAddr,omitempty" json:"ipAddr,omitempty"`
// Maximum transmission unit, must be 1-9216
// Used only to DPDK net type
// Required: false
MTU uint64 `url:"mtu,omitempty" json:"mtu,omitempty" validate:"omitempty,mtu"`
// MAC address to assign to this VM when connecting to the specified network
// Required: false
MAC string `url:"mac,omitempty" json:"mac,omitempty" validate:"omitempty"`
// SDN interface id
// Required: false
SDNInterfaceID string `url:"sdn_interface_id,omitempty" json:"sdn_interface_id,omitempty"`
// List of security group IDs to assign to this interface
// Required: false
SecGroups []uint64 `url:"security_groups,omitempty" json:"security_groups,omitempty"`
// Flag indicating whether security groups are enabled for this interface
// Required: false
EnableSecGroups bool `url:"enable_secgroups,omitempty" json:"enable_secgroups,omitempty"`
// Flag indicating whether this interface is enabled (only for VINS, EXTNET, DPDK, SDN, TRUNK)
// Required: false
Enabled interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
}
// MassCreateRequest struct to mass create KVM x86
type MassCreateRequest struct {
// ID of the resource group, which will own this VM
@@ -66,7 +107,7 @@ type MassCreateRequest struct {
// If not specified, compute will be created with default interface from RG.
// To create compute without interfaces, pass initialized empty slice .
// Required: false
Interfaces []Interface `url:"-" json:"interfaces,omitempty" validate:"omitempty,dive"`
Interfaces []InterfaceMassCreate `url:"-" json:"interfaces,omitempty" validate:"omitempty,dive"`
// Input data for cloud-init facility
// Required: false

View File

@@ -0,0 +1,42 @@
package node
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// UpdateDescriptionRequest struct to update description of the node
type UpdateDescriptionRequest struct {
// Node ID
// Required: true
NID uint64 `url:"nid" json:"nid" validate:"required"`
// New description for the node
// Required: true
Description string `url:"description" json:"description" validate:"required"`
}
// UpdateDescription updates description of the node
func (n Node) UpdateDescription(ctx context.Context, req UpdateDescriptionRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
return false, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/cloudbroker/node/update_description"
res, err := n.client.DecortApiCall(ctx, http.MethodPost, url, req)
if err != nil {
return false, err
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

View File

@@ -56,6 +56,9 @@ type ItemStoragePolicy struct {
// Max IOPS for the sotrage policy
LimitIOPS uint64 `json:"limit_iops"`
// Storage policy ID
StoragePolicyID uint64 `json:"storage_policy_id"`
// Which accounts and resource groups use the storage policy
Usage Usage `json:"usage"`
}
@@ -82,6 +85,9 @@ type InfoStoragePolicy struct {
// Max IOPS for the storage policy
LimitIOPS uint64 `json:"limit_iops"`
// Storage policy ID
StoragePolicyID uint64 `json:"storage_policy_id"`
// Which accounts and resource groups use the storage policy
Usage Usage `json:"usage"`
}

View File

@@ -122,6 +122,9 @@ type ItemInterface struct {
// Enabled
Enabled bool `json:"enabled"`
// Enable security groups
EnableSecGroups bool `json:"enable_secgroups"`
// FLIPGroup ID
FLIPGroupID uint64 `json:"flipgroupId"`
@@ -158,6 +161,9 @@ type ItemInterface struct {
// NodeID
NodeID int64 `json:"nodeId"`
// List of security groups
SecGroups []uint64 `json:"security_groups"`
// SDNInterfaceID
SDNInterfaceID string `json:"sdn_interface_id"`
@@ -241,6 +247,12 @@ type VNFDev struct {
// VNC password
VNCPassword string `json:"vncPasswd"`
// Zone ID
ZoneID uint64 `json:"zoneId"`
// Live migration job ID
LiveMigrationJobID uint64 `json:"live_migration_job_id"`
}
// Main information about reservation
@@ -421,6 +433,9 @@ type InfoVNF struct {
// Type
Type string `json:"type"`
// Zone ID
ZoneID uint64 `json:"zoneId"`
}
// List of static routes

View File

@@ -11,7 +11,7 @@ import (
// DeleteRequest struct to delete access group
type DeleteRequest struct {
// Comment of the access group
// ID of the access group
// Required: true
GroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
}

46
pkg/sdn/acsgroups/get.go Normal file
View File

@@ -0,0 +1,46 @@
package acsgroups
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// GetGroupRequest struct to get an access group
type GetGroupRequest struct {
// ID of the access group
// Required: true
GroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
}
// Info about access group
func (i AccessGroups) Get(ctx context.Context, req GetGroupRequest) (*AccessGroup, error) {
res, err := i.GetRaw(ctx, req)
if err != nil {
return nil, err
}
group := AccessGroup{}
err = json.Unmarshal(res, &group)
if err != nil {
return nil, err
}
return &group, nil
}
// GetRaw gets a details of group as an array of bytes
func (a AccessGroups) GetRaw(ctx context.Context, req GetGroupRequest) ([]byte, error) {
if err := validators.ValidateRequest(req); err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/access_group/get"
res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
return res, err
}

View File

@@ -9,8 +9,8 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// PatchRequest struct to update access group
type PatchRequest struct {
// UpdateRequest struct to update access group
type UpdateRequest struct {
// Access group ID
// Required: true
AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
@@ -25,13 +25,13 @@ type PatchRequest struct {
}
// Update updates a access groups
func (i AccessGroups) Patch(ctx context.Context, req PatchRequest) (*AccessGroup, error) {
func (i AccessGroups) Update(ctx context.Context, req UpdateRequest) (*AccessGroup, error) {
err := validators.ValidateRequest(req)
if err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/access_group/patch"
url := "/sdn/access_group/update"
res, err := i.client.DecortApiCallCtype(ctx, http.MethodPatch, url, constants.MIMEJSON, req)
if err != nil {

10
pkg/sdn/address_pools.go Normal file
View File

@@ -0,0 +1,10 @@
package sdn
import (
ap "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/adrspools"
)
// Accessing the SDN method group
func (sdn *SDN) AddressPools() *ap.AddressPools {
return ap.New(sdn.client)
}

View File

@@ -0,0 +1,18 @@
// API Actor API for managing SDN adress pools
package adrspools
import (
"repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
)
// Structure for creating request to address pools
type AddressPools struct {
client interfaces.Caller
}
// Builder for adress pools endpoints
func New(client interfaces.Caller) *AddressPools {
return &AddressPools{
client,
}
}

View File

@@ -0,0 +1,80 @@
package adrspools
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// CreateRequest struct to create address pool
type CreateRequest struct {
// ID of the access group
// Required: true
AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
// Description of the network
// Required: true
Description string `url:"description" json:"description" validate:"required"`
// Name of the network
// Required: true
Name string `url:"name" json:"name" validate:"required"`
// Network address type
// Required: true
NetAddressType string `url:"net_address_type" json:"net_address_type" validate:"required,addressPoolNetTypeValidator"`
// List of network addresses
// Required: false
NetAddresses []NetAddress `url:"net_addresses,omitempty" json:"net_addresses,omitempty" validate:"dive"`
}
// NetAddress struct representing network address
type NetAddress struct {
// Network address type
// Required: true
NetAddressType string `url:"net_address_type" json:"net_address_type" validate:"required,addressPoolNetTypeValidator"`
// IP address
// Required: true
IPAddr string `url:"ip_addr" json:"ip_addr" validate:"required"`
// End of IP address range
// Required: false
IPAddrRangeEnd string `url:"ip_addr_range_end,omitempty" json:"ip_addr_range_end,omitempty"`
// IP prefix
// Required: false
IPPrefix string `url:"ip_prefix,omitempty" json:"ip_prefix,omitempty"`
// MAC address
// Required: false
MACAddr string `url:"mac_addr,omitempty" json:"mac_addr,omitempty"`
}
// Create creates a address pool
func (i AddressPools) Create(ctx context.Context, req CreateRequest) (*NetworkAddressPool, error) {
err := validators.ValidateRequest(req)
if err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/address_pool/create"
res, err := i.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
if err != nil {
return nil, err
}
info := NetworkAddressPool{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}

View File

@@ -0,0 +1,51 @@
package adrspools
import (
"context"
"net/http"
"strconv"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// DeleteRequest struct to delete address pool
type DeleteRequest struct {
// Address pool ID
// Required: true
AddressPoolID string `url:"address_pool_id" json:"address_pool_id" validate:"required"`
// Version ID
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
// Force delete
// Required: false
Force interface{} `url:"force,omitempty" json:"force,omitempty" validate:"omitempty,isBool"`
}
// Delete an address pool
func (i AddressPools) Delete(ctx context.Context, req DeleteRequest) (bool, error) {
err := validators.ValidateRequest(req)
if err != nil {
return false, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/address_pool/delete"
res, err := i.client.DecortApiCallCtype(ctx, http.MethodDelete, url, constants.MIMEJSON, req)
if err != nil {
return false, err
}
if string(res) == "" {
return true, nil
}
result, err := strconv.ParseBool(string(res))
if err != nil {
return false, err
}
return result, nil
}

View File

@@ -0,0 +1,42 @@
package adrspools
// FilterByID returns AddressPoolsList with specified ID.
func (agl AddressPoolsList) FilterByID(id string) AddressPoolsList {
predicate := func(ia NetworkAddressPool) bool {
return ia.ID == id
}
return agl.FilterFunc(predicate)
}
// FilterByName returns AddressPoolsList with specified Name.
func (agl AddressPoolsList) FilterByName(name string) AddressPoolsList {
predicate := func(ia NetworkAddressPool) bool {
return ia.Name == name
}
return agl.FilterFunc(predicate)
}
// FilterFunc allows filtering AddressPoolsList based on a user-specified predicate.
func (agl AddressPoolsList) FilterFunc(predicate func(NetworkAddressPool) bool) AddressPoolsList {
var result AddressPoolsList
for _, acc := range agl.Pools {
if predicate(acc) {
result.Pools = append(result.Pools, acc)
}
}
return result
}
// FindOne returns first element.
// If none was found, returns an empty struct.
func (agl AddressPoolsList) FindOne() NetworkAddressPool {
if len(agl.Pools) == 0 {
return NetworkAddressPool{}
}
return agl.Pools[0]
}

View File

@@ -0,0 +1,151 @@
package adrspools
import (
"testing"
)
var testAddressPools = AddressPoolsList{
Pools: []NetworkAddressPool{
{
ID: "pool1",
Name: "DevelopersPool",
Description: "First pool",
CreatedAt: "2023-01-01",
AccessGroupID: "group1",
AccessGroupName: "Developers",
NetAddressType: "IPv4",
NetAddresses: []NetworkAddress{
{
ID: "addr1",
IPAddr: "192.168.1.1",
NetAddressType: "IPv4",
NetAddressPoolID: "pool1",
},
},
PoolCounters: PoolCounters{
SecurityRules: 5,
},
VersionID: 1,
},
{
ID: "pool2",
Name: "AdminsPool",
Description: "Second pool",
CreatedAt: "2023-01-02",
AccessGroupID: "group2",
AccessGroupName: "Admins",
NetAddressType: "IPv4",
NetAddresses: []NetworkAddress{
{
ID: "addr2",
IPAddr: "192.168.1.2",
NetAddressType: "IPv4",
NetAddressPoolID: "pool2",
},
},
PoolCounters: PoolCounters{
SecurityRules: 3,
},
VersionID: 2,
},
{
ID: "pool3",
Name: "UsersPool",
Description: "Third pool",
CreatedAt: "2023-01-03",
AccessGroupID: "group3",
AccessGroupName: "Users",
NetAddressType: "IPv6",
NetAddresses: []NetworkAddress{
{
ID: "addr3",
IPAddr: "2001:db8::1",
NetAddressType: "IPv6",
NetAddressPoolID: "pool3",
},
},
PoolCounters: PoolCounters{
SecurityRules: 7,
},
VersionID: 3,
},
},
}
func TestFilterByID(t *testing.T) {
actual := testAddressPools.FilterByID("pool2").FindOne()
if actual.ID != "pool2" {
t.Fatal("actual:", actual.ID, "> expected: pool2")
}
}
func TestFilterByName(t *testing.T) {
actual := testAddressPools.FilterByName("UsersPool").FindOne()
if actual.Name != "UsersPool" {
t.Fatal("actual:", actual.Name, ">> expected: UsersPool")
}
}
func TestFilterFunc(t *testing.T) {
actual := testAddressPools.FilterFunc(func(ap NetworkAddressPool) bool {
return ap.Description == "Second pool"
})
if len(actual.Pools) != 1 || actual.Pools[0].ID != "pool2" {
t.Fatal("Expected 1 pool with description 'Second pool', found:", len(actual.Pools))
}
}
func TestFindOneWithResults(t *testing.T) {
result := testAddressPools.FilterByID("pool1").FindOne()
if result.ID != "pool1" {
t.Fatal("Expected pool1, got:", result.ID)
}
}
func TestFindOneEmpty(t *testing.T) {
emptyList := AddressPoolsList{}
result := emptyList.FindOne()
if result.ID != "" || result.Name != "" {
t.Fatal("Expected empty NetworkAddressPool, got:", result)
}
}
func TestFilterByIDNotFound(t *testing.T) {
actual := testAddressPools.FilterByID("nonexistent")
if len(actual.Pools) != 0 {
t.Fatal("Expected 0 pools, found:", len(actual.Pools))
}
}
func TestFilterByNameNotFound(t *testing.T) {
actual := testAddressPools.FilterByName("Nonexistent Pool")
if len(actual.Pools) != 0 {
t.Fatal("Expected 0 pools, found:", len(actual.Pools))
}
}
func TestFilterByNetAddressType(t *testing.T) {
actual := testAddressPools.FilterFunc(func(ap NetworkAddressPool) bool {
return ap.NetAddressType == "IPv6"
})
if len(actual.Pools) != 1 || actual.Pools[0].ID != "pool3" {
t.Fatal("Expected 1 pool with IPv6 type, found:", len(actual.Pools))
}
}
func TestFilterBySecurityRulesCount(t *testing.T) {
actual := testAddressPools.FilterFunc(func(ap NetworkAddressPool) bool {
return ap.PoolCounters.SecurityRules > 4
})
if len(actual.Pools) != 2 {
t.Fatal("Expected 2 pools with more than 4 security rules, found:", len(actual.Pools))
}
}

47
pkg/sdn/adrspools/get.go Normal file
View File

@@ -0,0 +1,47 @@
package adrspools
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// GetRequest struct to get information about address group
type GetRequest struct {
// ID an address group
// Required: true
ID string `url:"address_pool_id" json:"address_pool_id" validate:"required"`
}
// Get gets address pool details as a NetworkAddressPool struct
func (a AddressPools) Get(ctx context.Context, req GetRequest) (*NetworkAddressPool, error) {
res, err := a.GetRaw(ctx, req)
if err != nil {
return nil, err
}
info := NetworkAddressPool{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}
// GetRaw gets address pool details as an array of bytes
func (a AddressPools) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
err := validators.ValidateRequest(req)
if err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/address_pool/get"
res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
return res, err
}

10
pkg/sdn/adrspools/ids.go Normal file
View File

@@ -0,0 +1,10 @@
package adrspools
// IDs gets array of IDs from AddressPoolsList struct
func (agl AddressPoolsList) IDs() []string {
res := make([]string, 0, len(agl.Pools))
for _, c := range agl.Pools {
res = append(res, c.ID)
}
return res
}

92
pkg/sdn/adrspools/list.go Normal file
View File

@@ -0,0 +1,92 @@
package adrspools
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// ListAddressPoolsRequest struct to get a list of a groups
type ListAddressPoolsRequest struct {
// Filter by access group ID
// Required: false
AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,omitempty"`
// Filter by name
// Required: false
Name string `url:"name,omitempty" json:"name,omitempty"`
// Filter by minimum address count (greater than or equal to)
// Required: false
AddrNumberMin uint64 `url:"addr_number_min,omitempty" json:"addr_number_min,omitempty"`
// Filter by maximum address count (less than)
// Required: false
AddrNumberMax uint64 `url:"addr_number_max,omitempty" json:"addr_number_max,omitempty"`
// Updated at lower bound (greater than or equal to)
// Required: false
UpdatedFrom string `url:"updated_from,omitempty" json:"updated_from,omitempty"`
// Updated at upper bound (less than)
// Required: false
UpdatedTo string `url:"updated_to,omitempty" json:"updated_to,omitempty"`
// Created at lower bound (greater than or equal to)
// Required: false
CreatedFrom string `url:"created_from,omitempty" json:"created_from,omitempty"`
// Created at upper bound (less than)
// Required: false
CreatedTo string `url:"created_to,omitempty" json:"created_to,omitempty"`
// Page number for pagination
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Number of results per page
// Required: false
PerPage uint64 `url:"per_page,omitempty" json:"per_page,omitempty"`
// Field to sort by (name, addr_count, created_at, updated_at)
// Required: false
SortBy string `url:"sort_by,omitempty" json:"sort_by,omitempty"`
// Sort order (asc/desc)
// Required: false
SortOrder string `url:"sort_order,omitempty" json:"sort_order,omitempty"`
}
// List of address pools
func (i AddressPools) List(ctx context.Context, req ListAddressPoolsRequest) (*AddressPoolsList, error) {
res, err := i.ListRaw(ctx, req)
if err != nil {
return nil, err
}
pools := []NetworkAddressPool{}
err = json.Unmarshal(res, &pools)
if err != nil {
return nil, err
}
result := AddressPoolsList{Pools: pools}
return &result, nil
}
// ListRaw gets a list of all address pools as an array of bytes
func (a AddressPools) ListRaw(ctx context.Context, req ListAddressPoolsRequest) ([]byte, error) {
if err := validators.ValidateRequest(req); err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/access_group/list"
res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
return res, err
}

View File

@@ -0,0 +1,62 @@
package adrspools
type AddressPoolsList struct {
Pools []NetworkAddressPool
}
// Main information about network address pool
type NetworkAddressPool struct {
// Access group ID
AccessGroupID string `json:"access_group_id"`
// Access group name
AccessGroupName string `json:"access_group_name"`
// Created time
CreatedAt string `json:"created_at"`
// Updated time
UpdatedAt string `json:"updated_at"`
// Description
Description string `json:"description"`
// ID
ID string `json:"id"`
// Name
Name string `json:"name"`
// Network address type
NetAddressType string `json:"net_address_type"`
// List of network addresses
NetAddresses []NetworkAddress `json:"net_addresses"`
// Pool counters
PoolCounters PoolCounters `json:"pool_counters"`
// Version ID
VersionID uint64 `json:"version_id"`
}
// Network address information
type NetworkAddress struct {
// ID
ID string `json:"id"`
// IP address
IPAddr string `json:"ip_addr"`
// Network address type
NetAddressType string `json:"net_address_type"`
// Network address pool ID
NetAddressPoolID string `json:"net_address_pool_id"`
}
// Pool counters information
type PoolCounters struct {
// Security rules count
SecurityRules uint64 `json:"security_rules"`
}

View File

@@ -0,0 +1,43 @@
package adrspools
import (
"encoding/json"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/serialization"
)
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (la AddressPoolsList) Serialize(params ...string) (serialization.Serialized, error) {
if len(la.Pools) == 0 {
return []byte{}, nil
}
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(la, prefix, indent)
}
return json.Marshal(la)
}
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (ia NetworkAddressPool) Serialize(params ...string) (serialization.Serialized, error) {
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(ia, prefix, indent)
}
return json.Marshal(ia)
}

View File

@@ -0,0 +1,84 @@
package adrspools
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// UpdateRequest struct to update address pool
type UpdateRequest struct {
// ID of the address pool
// Required: true
AddressPoolID string `url:"address_pool_id" json:"address_pool_id" validate:"required"`
// ID of the version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
// Description of the network
// Required: true
Description string `url:"description" json:"description" validate:"required"`
// Name of the network
// Required: true
Name string `url:"name" json:"name" validate:"required"`
// Network address type
// Required: true
NetAddressType string `url:"net_address_type" json:"net_address_type" validate:"required,addressPoolNetTypeValidator"`
// List of network addresses
// Required: false
NetAddresses []UpdateNetAddress `url:"net_addresses,omitempty" json:"net_addresses,omitempty" validate:"dive"`
}
// UpdateNetAddress struct representing network address
type UpdateNetAddress struct {
// Network address type
// Required: true
NetAddressType string `url:"net_address_type" json:"net_address_type" validate:"required,addressPoolNetTypeValidator"`
// IP address
// Required: true
IPAddr string `url:"ip_addr" json:"ip_addr" validate:"required"`
// End of IP address range
// Required: false
IPAddrRangeEnd string `url:"ip_addr_range_end,omitempty" json:"ip_addr_range_end,omitempty"`
// IP prefix
// Required: false
IPPrefix string `url:"ip_prefix,omitempty" json:"ip_prefix,omitempty"`
// MAC address
// Required: false
MACAddr string `url:"mac_addr,omitempty" json:"mac_addr,omitempty"`
}
// Update updates a address pool
func (i AddressPools) Update(ctx context.Context, req UpdateRequest) (*NetworkAddressPool, error) {
err := validators.ValidateRequest(req)
if err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/address_pool/update"
res, err := i.client.DecortApiCallCtype(ctx, http.MethodPut, url, constants.MIMEJSON, req)
if err != nil {
return nil, err
}
info := NetworkAddressPool{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}

View File

@@ -0,0 +1,10 @@
package sdn
import (
dsp "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/defsecpolicies"
)
// Accessing the SDN method group
func (sdn *SDN) DefaultSecurityPolicies() *dsp.DefaultSecurityPolicies {
return dsp.New(sdn.client)
}

View File

@@ -0,0 +1,18 @@
// API Actor API for managing SDN default secirity policies
package defsecpolicies
import (
"repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
)
// Structure for creating request to default security policies
type DefaultSecurityPolicies struct {
client interfaces.Caller
}
// Builder for adress pools endpoints
func New(client interfaces.Caller) *DefaultSecurityPolicies {
return &DefaultSecurityPolicies{
client,
}
}

View File

@@ -0,0 +1,42 @@
package defsecpolicies
// FilterByID returns SecurityPoliciesList with specified ID.
func (agl SecurityPoliciesList) FilterByID(id string) SecurityPoliciesList {
predicate := func(ia SecurityPolicy) bool {
return ia.ID == id
}
return agl.FilterFunc(predicate)
}
// FilterByName returns SecurityPoliciesList with specified Name.
func (agl SecurityPoliciesList) FilterByName(name string) SecurityPoliciesList {
predicate := func(ia SecurityPolicy) bool {
return ia.DisplayName == name
}
return agl.FilterFunc(predicate)
}
// FilterFunc allows filtering SecurityPoliciesList based on a user-specified predicate.
func (agl SecurityPoliciesList) FilterFunc(predicate func(SecurityPolicy) bool) SecurityPoliciesList {
var result SecurityPoliciesList
for _, acc := range agl.Policies {
if predicate(acc) {
result.Policies = append(result.Policies, acc)
}
}
return result
}
// FindOne returns first element.
// If none was found, returns an empty struct.
func (agl SecurityPoliciesList) FindOne() SecurityPolicy {
if len(agl.Policies) == 0 {
return SecurityPolicy{}
}
return agl.Policies[0]
}

View File

@@ -0,0 +1,268 @@
package defsecpolicies
import (
"testing"
)
var testSecurityPolicies = SecurityPoliciesList{
Policies: []SecurityPolicy{
{
ID: "policy1",
DisplayName: "DevelopersPolicy",
Description: "First policy",
CreatedAt: "2023-01-01T00:00:00Z",
UpdatedAt: "2023-01-01T01:00:00Z",
AccessGroupID: "group1",
DefaultACLDrop: "DROP",
DefaultOpenSessionDrop: true,
SecurityRules: []SecurityRule{
{
ID: "rule1",
DisplayName: "DevRule1",
Action: "ALLOW",
Direction: "INGRESS",
Enabled: true,
Priority: 100,
SecurityPolicyID: "policy1",
VersionID: 1,
},
},
Status: Status{
Common: "ACTIVE",
Hypervisors: []HypervisorStatus{
{
Name: "hv1",
DisplayName: "Hypervisor1",
Status: "SYNCED",
HypervisorStatus: "HEALTHY",
SyncedAt: "2023-01-01T01:00:00Z",
},
},
},
VersionID: 1,
},
{
ID: "policy2",
DisplayName: "AdminsPolicy",
Description: "Second policy",
CreatedAt: "2023-01-02T00:00:00Z",
UpdatedAt: "2023-01-02T01:00:00Z",
AccessGroupID: "group2",
DefaultACLDrop: "REJECT",
DefaultOpenSessionDrop: false,
SecurityRules: []SecurityRule{
{
ID: "rule2",
DisplayName: "AdminRule1",
Action: "DENY",
Direction: "EGRESS",
Enabled: true,
Priority: 50,
SecurityPolicyID: "policy2",
VersionID: 1,
},
},
Status: Status{
Common: "ACTIVE",
Hypervisors: []HypervisorStatus{
{
Name: "hv2",
DisplayName: "Hypervisor2",
Status: "SYNCED",
HypervisorStatus: "HEALTHY",
SyncedAt: "2023-01-02T01:00:00Z",
},
},
},
VersionID: 2,
},
{
ID: "policy3",
DisplayName: "UsersPolicy",
Description: "Third policy",
CreatedAt: "2023-01-03T00:00:00Z",
UpdatedAt: "2023-01-03T01:00:00Z",
AccessGroupID: "group3",
DefaultACLDrop: "DROP",
DefaultOpenSessionDrop: true,
SecurityRules: []SecurityRule{
{
ID: "rule3",
DisplayName: "UserRule1",
Action: "ALLOW",
Direction: "INGRESS",
Enabled: false,
Priority: 200,
SecurityPolicyID: "policy3",
VersionID: 1,
},
},
Status: Status{
Common: "PENDING",
Hypervisors: []HypervisorStatus{
{
Name: "hv3",
DisplayName: "Hypervisor3",
Status: "SYNCING",
HypervisorStatus: "HEALTHY",
SyncedAt: "2023-01-03T01:00:00Z",
},
},
},
VersionID: 3,
},
},
}
func TestFilterByID(t *testing.T) {
actual := testSecurityPolicies.FilterByID("policy2").FindOne()
if actual.ID != "policy2" {
t.Fatal("actual:", actual.ID, "> expected: policy2")
}
}
func TestFilterByDisplayName(t *testing.T) {
actual := testSecurityPolicies.FilterByName("UsersPolicy").FindOne()
if actual.DisplayName != "UsersPolicy" {
t.Fatal("actual:", actual.DisplayName, ">> expected: UsersPolicy")
}
}
func TestFilterFunc(t *testing.T) {
actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicy) bool {
return sp.Description == "Second policy"
})
if len(actual.Policies) != 1 || actual.Policies[0].ID != "policy2" {
t.Fatal("Expected 1 policy with description 'Second policy', found:", len(actual.Policies))
}
}
func TestFindOneWithResults(t *testing.T) {
result := testSecurityPolicies.FilterByID("policy1").FindOne()
if result.ID != "policy1" {
t.Fatal("Expected policy1, got:", result.ID)
}
}
func TestFindOneEmpty(t *testing.T) {
emptyList := SecurityPoliciesList{}
result := emptyList.FindOne()
if result.ID != "" || result.DisplayName != "" {
t.Fatal("Expected empty SecurityPolicy, got:", result)
}
}
func TestFilterByIDNotFound(t *testing.T) {
actual := testSecurityPolicies.FilterByID("nonexistent")
if len(actual.Policies) != 0 {
t.Fatal("Expected 0 policies, found:", len(actual.Policies))
}
}
func TestFilterByDisplayNameNotFound(t *testing.T) {
actual := testSecurityPolicies.FilterByName("Nonexistent Policy")
if len(actual.Policies) != 0 {
t.Fatal("Expected 0 policies, found:", len(actual.Policies))
}
}
func TestFilterByDefaultACLDrop(t *testing.T) {
actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicy) bool {
return sp.DefaultACLDrop == "DROP"
})
if len(actual.Policies) != 2 {
t.Fatal("Expected 2 policies with DROP default ACL, found:", len(actual.Policies))
}
}
func TestFilterByDefaultOpenSessionDrop(t *testing.T) {
actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicy) bool {
return sp.DefaultOpenSessionDrop == true
})
if len(actual.Policies) != 2 {
t.Fatal("Expected 2 policies with default open session drop enabled, found:", len(actual.Policies))
}
}
func TestFilterByStatus(t *testing.T) {
actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicy) bool {
return sp.Status.Common == "ACTIVE"
})
if len(actual.Policies) != 2 {
t.Fatal("Expected 2 policies with ACTIVE status, found:", len(actual.Policies))
}
}
func TestFilterByAccessGroupID(t *testing.T) {
actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicy) bool {
return sp.AccessGroupID == "group1"
})
if len(actual.Policies) != 1 || actual.Policies[0].ID != "policy1" {
t.Fatal("Expected 1 policy with access group ID 'group1', found:", len(actual.Policies))
}
}
func TestFilterByRuleAction(t *testing.T) {
actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicy) bool {
for _, rule := range sp.SecurityRules {
if rule.Action == "ALLOW" {
return true
}
}
return false
})
if len(actual.Policies) != 2 {
t.Fatal("Expected 2 policies with ALLOW rules, found:", len(actual.Policies))
}
}
func TestFilterByRuleDirection(t *testing.T) {
actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicy) bool {
for _, rule := range sp.SecurityRules {
if rule.Direction == "INGRESS" {
return true
}
}
return false
})
if len(actual.Policies) != 2 {
t.Fatal("Expected 2 policies with INGRESS rules, found:", len(actual.Policies))
}
}
func TestFilterByRuleEnabled(t *testing.T) {
actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicy) bool {
for _, rule := range sp.SecurityRules {
if rule.Enabled {
return true
}
}
return false
})
if len(actual.Policies) != 2 {
t.Fatal("Expected 2 policies with enabled rules, found:", len(actual.Policies))
}
}
func TestFilterByVersionID(t *testing.T) {
actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicy) bool {
return sp.VersionID > 1
})
if len(actual.Policies) != 2 {
t.Fatal("Expected 2 policies with version ID > 1, found:", len(actual.Policies))
}
}

View File

@@ -0,0 +1,10 @@
package defsecpolicies
// IDs gets array of IDs from SecurityPoliciesList struct
func (spl SecurityPoliciesList) IDs() []string {
res := make([]string, 0, len(spl.Policies))
for _, c := range spl.Policies {
res = append(res, c.ID)
}
return res
}

View File

@@ -0,0 +1,64 @@
package defsecpolicies
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// ListRequest struct to get a list of default security group
type ListRequest struct {
// Filter by access group ID
// Required: false
AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,omitempty"`
// Page number for pagination
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Number of results per page
// Required: false
PerPage uint64 `url:"per_page,omitempty" json:"per_page,omitempty"`
// Field to sort by (name, addr_count, created_at, updated_at)
// Required: false
SortBy string `url:"sort_by,omitempty" json:"sort_by,omitempty"`
// Sort order (asc/desc)
// Required: false
SortOrder string `url:"sort_order,omitempty" json:"sort_order,omitempty"`
}
// List of default security policies
func (i DefaultSecurityPolicies) List(ctx context.Context, req ListRequest) (*SecurityPoliciesList, error) {
res, err := i.ListRaw(ctx, req)
if err != nil {
return nil, err
}
policies := []SecurityPolicy{}
err = json.Unmarshal(res, &policies)
if err != nil {
return nil, err
}
result := SecurityPoliciesList{Policies: policies}
return &result, nil
}
// ListRaw gets a list of all default security policies as an array of bytes
func (a DefaultSecurityPolicies) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
if err := validators.ValidateRequest(req); err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/default_security_policy/list"
res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
return res, err
}

View File

@@ -0,0 +1,191 @@
package defsecpolicies
type SecurityPoliciesList struct {
Policies []SecurityPolicy `json:"policies"`
}
// Main information about security policy
type SecurityPolicy struct {
// Access group ID
AccessGroupID string `json:"access_group_id"`
// Created time
CreatedAt string `json:"created_at"`
// Default ACL drop behavior
DefaultACLDrop string `json:"default_acl_drop"`
// Default open session drop flag
DefaultOpenSessionDrop bool `json:"default_open_session_drop"`
// Description
Description string `json:"description"`
// Display name
DisplayName string `json:"display_name"`
// ID
ID string `json:"id"`
// Security rules
SecurityRules []SecurityRule `json:"security_rules"`
// Locked time
LockedAt string `json:"locked_at"`
// Status information
Status Status `json:"status"`
// Version ID
VersionID uint64 `json:"version_id"`
// Updated time
UpdatedAt string `json:"updated_at"`
}
// Security rule information
type SecurityRule struct {
// Access group ID
AccessGroupID string `json:"access_group_id"`
// Action
Action string `json:"action"`
// Description
Description string `json:"description"`
// Destination network object
DestinationNetObject NetObject `json:"destination_net_object"`
// Direction
Direction string `json:"direction"`
// Display name
DisplayName string `json:"display_name"`
// Enabled flag
Enabled bool `json:"enabled"`
// Filter configuration
Filter Filter `json:"filter"`
// ID
ID string `json:"id"`
// Log enabled flag
LogEnabled bool `json:"log_enabled"`
// Log name
LogName string `json:"log_name"`
// Log severity
LogSeverity string `json:"log_severity"`
// Priority
Priority int `json:"priority"`
// Security policy ID
SecurityPolicyID string `json:"security_policy_id"`
// Source network object
SourceNetObject NetObject `json:"source_net_object"`
// Statistics enabled flag
StatisticsEnabled bool `json:"statistics_enabled"`
// Version ID
VersionID uint64 `json:"version_id"`
}
// Network object information
type NetObject struct {
// Display name
DisplayName string `json:"display_name"`
// Network address pool ID
NetAddressPoolID string `json:"net_address_pool_id"`
// Network object group ID
NetObjectGroupID string `json:"net_object_group_id"`
}
// Filter configuration
type Filter struct {
// Filter parameters
Filters FilterParams `json:"filters"`
// Name
Name string `json:"name"`
}
// Filter parameters
type FilterParams struct {
// All protocols flag
All bool `json:"all"`
// ARP protocol flag
ARP bool `json:"arp"`
// DHCP protocol flag
DHCP bool `json:"dhcp"`
// Filter expression
Expression string `json:"expression"`
// ICMP protocol flag
ICMP bool `json:"icmp"`
// IP protocol flag
IP bool `json:"ip"`
// IPv4 protocol flag
IPv4 bool `json:"ip_v4"`
// IPv6 protocol flag
IPv6 bool `json:"ip_v6"`
// Keep opened sessions flag
KeepOpenedSessions bool `json:"keep_opened_sessions"`
// ND protocol flag
ND bool `json:"nd"`
// TCP protocol flag
TCP bool `json:"tcp"`
// TCP destination ports
TCPDstPorts []string `json:"tcp_dst_ports"`
// UDP protocol flag
UDP bool `json:"udp"`
// UDP destination ports
UDPDstPorts []string `json:"udp_dst_ports"`
}
// Status information
type Status struct {
// Common status
Common string `json:"common"`
// Hypervisor statuses
Hypervisors []HypervisorStatus `json:"hypervisors"`
}
// Hypervisor status information
type HypervisorStatus struct {
// Status
Status string `json:"status"`
// Name
Name string `json:"name"`
// Display name
DisplayName string `json:"display_name"`
// Hypervisor status
HypervisorStatus string `json:"hypervisor_status"`
// Last sync time
SyncedAt string `json:"synced_at"`
}

View File

@@ -0,0 +1,43 @@
package defsecpolicies
import (
"encoding/json"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/serialization"
)
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (la SecurityPoliciesList) Serialize(params ...string) (serialization.Serialized, error) {
if len(la.Policies) == 0 {
return []byte{}, nil
}
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(la, prefix, indent)
}
return json.Marshal(la)
}
// Serialize returns JSON-serialized []byte. Used as a wrapper over json.Marshal and json.MarshalIndent functions.
//
// In order to serialize with indent make sure to follow these guidelines:
// - First argument -> prefix
// - Second argument -> indent
func (ia SecurityPolicy) Serialize(params ...string) (serialization.Serialized, error) {
if len(params) > 1 {
prefix := params[0]
indent := params[1]
return json.MarshalIndent(ia, prefix, indent)
}
return json.Marshal(ia)
}

View File

@@ -0,0 +1,53 @@
package defsecpolicies
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// UpdateRequest struct to update default security policy
type UpdateRequest struct {
// ID of the access group
// Required: true
AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
// ID of the version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
// Default ACL drop behavior
// Required: false
DefaultACLDrop string `url:"default_acl_drop,omitempty" json:"default_acl_drop,omitempty"`
// Default open session drop flag
// Required: false
DefaultOpenSessionDrop bool `url:"default_open_session_drop,omitempty" json:"default_open_session_drop,omitempty"`
}
// Update updates a default security policy
func (i DefaultSecurityPolicies) Update(ctx context.Context, req UpdateRequest) (*SecurityPolicy, error) {
err := validators.ValidateRequest(req)
if err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/default_security_policy/update"
res, err := i.client.DecortApiCallCtype(ctx, http.MethodPatch, url, constants.MIMEJSON, req)
if err != nil {
return nil, err
}
info := SecurityPolicy{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}

10
pkg/sdn/extnet.go Normal file
View File

@@ -0,0 +1,10 @@
package sdn
import (
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/extnet"
)
// Accessing the ExtNet method group
func (sdn *SDN) ExtNet() *extnet.ExtNet {
return extnet.New(sdn.client)
}

729
pkg/sdn/extnet/create.go Normal file
View File

@@ -0,0 +1,729 @@
package extnet
import (
"context"
"encoding/json"
"net/http"
"time"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// CreateRequest struct for creating account
type CreateRequest struct {
// Name of the bridge network
// Required: true
BridgeNetworkName string `url:"bridge_network_name" json:"bridge_network_name" validate:"required"`
// Detailed description of the external network
// Required: true
Description string `url:"description" json:"description" validate:"required"`
// User-friendly name for the external network
// Required: true
DisplayName string `url:"display_name" json:"display_name" validate:"required"`
// Whether the network is enabled
// Required: true
Enabled bool `url:"enabled" json:"enabled"`
// List of hypervisor names
// Required: true
Hypervisors []string `url:"hypervisors" json:"hypervisors" validate:"required"`
// List of external network ports
// Required: false
ExternalNetworkPorts []ExternalNetworkPortRequest `url:"-" json:"external_network_ports,omitempty"`
// IPv4 default gateway address
// Required: false
DefaultGatewayIPv4 string `url:"default_gateway_ipv4,omitempty" json:"default_gateway_ipv4,omitempty"`
// IPv6 default gateway address
// Required: false
DefaultGatewayIPv6 string `url:"default_gateway_ipv6,omitempty" json:"default_gateway_ipv6,omitempty"`
// IPv4 subnet in CIDR notation (Either subnet_v4 or subnet_v6 must be specified)
// Required: false
SubnetV4 string `url:"subnet_v4,omitempty" json:"subnet_v4,omitempty"`
// IPv6 subnet in CIDR notation (Either subnet_v4 or subnet_v6 must be specified)
// Required: false
SubnetV6 string `url:"subnet_v6,omitempty" json:"subnet_v6,omitempty"`
// VLAN tag identifier
// Required: false
VLANTag string `url:"vlan_tag,omitempty" json:"vlan_tag,omitempty" validate:"omitempty,trunkTags"`
}
type ExternalNetworkPortRequest struct {
// Access group ID
// Required: true
AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
// Access group name
// Required: false
AccessGroupName string `url:"access_group_name,omitempty" json:"access_group_name,omitempty"`
// Comment for the external network port
// Required: true
Comment string `url:"comment" json:"comment" validate:"required"`
// User-friendly name for the external network port
// Required: true
DisplayName string `url:"display_name" json:"display_name" validate:"required"`
// Whether the network pork is enabled
// Required: true
Enabled bool `url:"enabled" json:"enabled"`
// IPv4
// Required: false
IPv4 string `url:"ipv4,omitempty" json:"ipv4,omitempty"`
// IPv6
// Required: false
IPv6 string `url:"ipv6,omitempty" json:"ipv6,omitempty"`
// IPv6 Config
// Required: false
IPv6Config *IPv6ConfigRequest `url:"-" json:"ipv6_config,omitempty"`
// MAC address
// Required: true
MAC string `url:"mac" json:"mac" validate:"required"`
// Router gateway port
// Required: false
RouterGatewayPort *RouterGatewayPortRequest `url:"-" json:"router_gateway_port,omitempty"`
// Floating IP
// Required: false
FloatingIP *FloatingIPRequest `url:"-" json:"floating_ip,omitempty"`
}
type IPv6ConfigRequest struct {
//Address Mode (Slaac or DhcpV6Stateful)
// Required: true
AddressMode string `url:"address_mode" json:"address_mode" validate:"required"`
// If true, the port will periodically send RA packets.
// Required: true
EnablePeriodicRa bool `url:"enable_periodic_ra" json:"enable_periodic_ra"`
// The number of waiting seconds between sending periodic RA
// Required: true
IntervalRa int64 `url:"interval_ra" json:"interval_ra" validate:"required"`
// The Default Router Preference (PRF) indicates whether this router should be preferred over other default routers.
// high, low, medium
// Required: true
RouterPreference string `url:"router_preference" json:"router_preference" validate:"required"`
}
type RouterGatewayPortRequest struct {
// Created at
// Required: true
CreatedAt time.Time `url:"created_at" json:"created_at" validate:"required"`
// Description
// Required: true
Description string `url:"description" json:"description" validate:"required"`
// Port id
// Required: true
ID string `url:"id" json:"id" validate:"required"`
// User-friendly name for the external network port
// Required: true
RouterDisplayName string `url:"router_display_name" json:"router_display_name" validate:"required"`
// Router ID
// Required: true
RouterID string `url:"router_id" json:"router_id" validate:"required"`
// SNAT Enabled
// Required: true
SNATEnabled bool `url:"snat_enabled" json:"snat_enabled"`
// Updated at
// Required: true
UpdatedAt time.Time `url:"updated_at" json:"updated_at" validate:"required"`
}
type FloatingIPRequest struct {
// Access group ID
// Required: true
AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
// Access group name
// Required: true
AccessGroupName string `url:"access_group_name" json:"access_group_name" validate:"required"`
// Created at
// Required: true
CreatedAt time.Time `url:"created_at" json:"created_at" validate:"required"`
// External network port
// Required: true
ExternalNetworkPort string `url:"external_network_port" json:"external_network_port" validate:"required"`
// ID of the Floating IP
// Required: true
ID string `url:"id" json:"id" validate:"required"`
// Logical port
// Required: false
LogicalPort *LogicalPortRequest `url:"-" json:"logical_port,omitempty"`
// Router
// Required: true
Router *RouterRequest `url:"-" json:"router" validate:"required"`
// Updated at
// Required: true
UpdatedAt time.Time `url:"updated_at" json:"updated_at" validate:"required"`
// ID of version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
}
type LogicalPortRequest struct {
// Logical Port ID
// Required: true
ID string `url:"id" json:"id" validate:"required"`
// Access group ID
// Required: true
AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
// Access group name
// Required: true
AccessGroupName string `url:"access_group_name" json:"access_group_name" validate:"required"`
// MAC of adapter
// Required: true
AdapterMAC string `url:"adapter_mac" json:"adapter_mac" validate:"required"`
// Address detection
// Required: true
AddressDetection bool `url:"address_detection" json:"address_detection" validate:"required"`
// Description
// Required: true
Description string `url:"description" json:"description" validate:"required"`
// Created at
// Required: false
CreatedAt time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
// User-friendly name for router
// Required: true
RouterDisplayName string `url:"router_display_name" json:"router_display_name" validate:"required"`
// Whether the logical pork is enabled
// Required: true
Enabled bool `url:"enabled" json:"enabled"`
// External Network ID
// Required: false
ExternalNetworkID string `url:"external_network_id,omitempty" json:"external_network_id,omitempty"`
// Hypervisor
// Required: true
Hypervisor string `url:"hypervisor" json:"hypervisor" validate:"required"`
// User-friendly name for hypervisor
// Required: false
HypervisorDisplayName string `url:"hypervisor_display_name,omitempty" json:"hypervisor_display_name,omitempty"`
// Live Migration Target Hv
// Required: true
LiveMigrationTargetHV string `url:"live_migration_target_hv" json:"live_migration_target_hv" validate:"required"`
// Status
// Required: true
Status *StatusRequest `url:"-" json:"status" validate:"required"`
// Port bindings
// Required: true
Bindings *PortBindingsRequest `url:"-" json:"bindings" validate:"required"`
// Unique Identifier
// Required: true
UniqueIDentifier string `url:"unique_identifier" json:"unique_identifier" validate:"required"`
// Updated at
// Required: true
UpdatedAt time.Time `url:"updated_at" json:"updated_at" validate:"required"`
// ID of version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
}
type StatusRequest struct {
// Common
// Required: true
Common string `url:"common" json:"common" validate:"required"`
// Hypervisors status
// Required: false
Hypervisors []HypervisorStatusRequest `url:"-" json:"hypervisors,omitempty"`
}
type PortBindingsRequest struct {
// Binding ID
// Required: true
ID string `url:"id" json:"id" validate:"required"`
// User-friendly name for segment
// Required: true
SegmentDisplayName string `url:"segment_display_name" json:"segment_display_name" validate:"required"`
// Segment ID
// Required: true
SegmentID string `url:"segment_id" json:"segment_id" validate:"required"`
// Port security
// Required: true
PortSecurity bool `url:"port_security" json:"port_security" validate:"required"`
// Address detection
// Required: true
AddressDetection bool `url:"address_detection" json:"address_detection" validate:"required"`
// Is Exclude From Firewall
// Required: true
IsExcludedFromFirewall bool `url:"is_excluded_from_firewall" json:"is_excluded_from_firewall" validate:"required"`
// ID of version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
// Created at
// Required: true
CreatedAt time.Time `url:"created_at" json:"created_at" validate:"required"`
// Updated at
// Required: true
UpdatedAt time.Time `url:"updated_at" json:"updated_at" validate:"required"`
// Logical port addresses
// Required: true
LogicalPortAddresses []LogicalPortAddressRequest `url:"-" json:"logical_port_addresses" validate:"required"`
}
type HypervisorStatusRequest struct {
// Status
// Required: true
Status string `url:"status" json:"status" validate:"required"`
// Name of hypervisor
// Required: true
Name string `url:"name" json:"name" validate:"required"`
// User-friendly name for the hypervisor
// Required: true
DisplayName string `url:"display_name" json:"display_name" validate:"required"`
// Hypervisor status
// Required: true
HypervisorStatus string `url:"hypervisor_status" json:"hypervisor_status" validate:"required"`
// Synced at
// Required: true
SyncedAt time.Time `url:"synced_at" json:"synced_at" validate:"required"`
}
type LogicalPortAddressRequest struct {
// IP of port
// Required: true
IP string `url:"ip" json:"ip" validate:"required"`
// IP type (IPv4 or IPv6)
// Required: true
IPType string `url:"ip_type" json:"ip_type" validate:"required"`
// Is discovered
// Required: false
IsDiscovered bool `url:"is_discovered,omitempty" json:"is_discovered,omitempty"`
// Is discovered
// Required: true
IsPrimary bool `url:"is_primary" json:"is_primary" validate:"required"`
// MAC
// Required: false
MAC string `url:"mac,omitempty" json:"mac,omitempty"`
// ID
// Required: true
ID string `url:"id" json:"id" validate:"required"`
// Logical port id
// Required: true
LogicalPortID string `url:"logical_port_id" json:"logical_port_id" validate:"required"`
// Assigned at
// Required: false
AssignedAt time.Time `url:"assigned_at,omitempty" json:"assigned_at,omitempty"`
}
type RouterRequest struct {
// Access group ID
// Required: false
AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,omitempty"`
// Access group name
// Required: false
AccessGroupName string `url:"access_group_name,omitempty" json:"access_group_name,omitempty"`
// Created at
// Required: false
CreatedAt time.Time `url:"created_at,omitempty" json:"created_at,omitempty"`
// Detailed description of the router
// Required: false
Description string `url:"description,omitempty" json:"description,omitempty"`
// User-friendly name for the router
// Required: true
DisplayName string `url:"display_name" json:"display_name" validate:"required"`
// Whether the router is enabled
// Required: false
Enabled bool `url:"enabled,omitempty" json:"enabled,omitempty"`
// Gateway ports
// Required: false
GatewayPorts []GatewayPortRequest `url:"-" json:"gateway_ports,omitempty"`
// ID
// Required: true
ID string `url:"id" json:"id" validate:"required"`
// Policies
// Required: false
Policies []RouterPolicyRequest `url:"-" json:"policies,omitempty"`
// Ports
// Required: false
Ports []RouterPortRequest `url:"-" json:"ports,omitempty"`
// Status
// Required: false
Status *StatusRequest `url:"-" json:"status,omitempty"`
// Updated at
// Required: false
UpdatedAt time.Time `url:"updated_at,omitempty" json:"updated_at,omitempty"`
// ID of version
// Required: false
VersionID uint64 `url:"version_id,omitempty" json:"version_id,omitempty"`
}
type GatewayPortRequest struct {
// Created at
// Required: true
CreatedAt time.Time `url:"created_at" json:"created_at" validate:"required"`
// Description
// Required: true
Description string `url:"description" json:"description" validate:"required"`
// L4 port max
// Required: false
ExternalL4PortMax int64 `url:"external_l4_port_max,omitempty" json:"external_l4_port_max,omitempty"`
// L4 port min
// Required: false
ExternalL4PortMin int64 `url:"external_l4_port_min,omitempty" json:"external_l4_port_min,omitempty"`
// External network port
// Required: true
ExternalNetworkPort interface{} `url:"external_network_port" json:"external_network_port" validate:"required"`
// ID of port
// Required: false
ID string `url:"id,omitempty" json:"id,omitempty"`
// SNAT Enabled
// Required: true
SNATEnabled bool `url:"snat_enabled" json:"snat_enabled"`
// Status
// Required: false
Status *StatusRequest `url:"-" json:"status,omitempty"`
// Updated at
// Required: true
UpdatedAt time.Time `url:"updated_at" json:"updated_at" validate:"required"`
// ID of version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
}
type RouterPolicyRequest struct {
// Action
// Required: true
Action string `url:"action" json:"action" validate:"required"`
// Created at
// Required: true
CreatedAt time.Time `url:"created_at" json:"created_at" validate:"required"`
// User-friendly name for the policy
// Required: true
DisplayName string `url:"display_name" json:"display_name" validate:"required"`
// Whether the policy is enabled
// Required: false
Enabled bool `url:"enabled,omitempty" json:"enabled,omitempty"`
// ID of port
// Required: false
ID string `url:"id,omitempty" json:"id,omitempty"`
// Match
// Required: true
Match interface{} `url:"match" json:"match" validate:"required"`
// Next IPv4 address
// Required: true
NextIPv4Address []string `url:"next_ipv4_address" json:"next_ipv4_address" validate:"required"`
// Next IPv6 address
// Required: true
NextIPv6Address []string `url:"next_ipv6_address" json:"next_ipv6_address" validate:"required"`
// Priority
// Required: true
Priority int64 `url:"priority" json:"priority" validate:"required"`
// Updated at
// Required: true
UpdatedAt time.Time `url:"updated_at" json:"updated_at" validate:"required"`
// ID of version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
}
type RouterPortRequest struct {
// Created at
// Required: true
CreatedAt time.Time `url:"created_at" json:"created_at" validate:"required"`
// Detailed description of the router port
// Required: true
Description string `url:"description" json:"description" validate:"required"`
// Whether the router port is enabled
// Required: true
Enabled bool `url:"enabled" json:"enabled"`
// ID of port
// Required: false
ID string `url:"id,omitempty" json:"id,omitempty"`
// Next IPv4 address
// Required: true
NextIPv4Address []string `url:"next_ipv4_address" json:"next_ipv4_address" validate:"required"`
// Next IPv6 address
// Required: true
NextIPv6Address []string `url:"next_ipv6_address" json:"next_ipv6_address" validate:"required"`
// IPv6 Config
// Required: true
IPv6Config *IPv6ConfigRequest `url:"-" json:"ipv6_config" validate:"required"`
// MAC address
// Required: true
MAC string `url:"mac" json:"mac" validate:"required"`
// Segment
// Required: true
Segment *SegmentRequest `url:"-" json:"segment" validate:"required"`
// Segment ID
// Required: true
SegmentID string `url:"segment_id" json:"segment_id" validate:"required"`
// Status
// Required: false
Status *StatusRequest `url:"-" json:"status,omitempty"`
// Updated at
// Required: true
UpdatedAt time.Time `url:"updated_at" json:"updated_at" validate:"required"`
// ID of version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
}
type SegmentRequest struct {
// Access group ID
// Required: true
AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
// Access group name
// Required: false
AccessGroupName string `url:"access_group_name,omitempty" json:"access_group_name,omitempty"`
// Created at
// Required: true
CreatedAt time.Time `url:"created_at" json:"created_at" validate:"required"`
// Detailed description of the router port
// Required: true
Description string `url:"description" json:"description" validate:"required"`
// DHCP IPv4
// Required: false
DHCPv4 *DHCPv4ConfigRequest `url:"-" json:"dhcp_v4,omitempty"`
// DHCP IPv6
// Required: false
DHCPv6 *DHCPv6ConfigRequest `url:"-" json:"dhcp_v6,omitempty"`
// User-friendly name for the segment
// Required: true
DisplayName string `url:"display_name" json:"display_name" validate:"required"`
// Whether the segment is enabled
// Required: false
Enabled bool `url:"enabled,omitempty" json:"enabled,omitempty"`
// ID of segment
// Required: false
ID string `url:"id,omitempty" json:"id,omitempty"`
// Logical ports info
// Required: false
LogicalPortsInfo []EntityInfoRequest `url:"-" json:"logical_ports_info,omitempty"`
// Routers info
// Required: false
RoutersInfo []EntityInfoRequest `url:"-" json:"routers_info,omitempty"`
// Status
// Required: false
Status *StatusRequest `url:"-" json:"status,omitempty"`
// IPv4 subnet in CIDR notation (Either subnet_v4 or subnet_v6 must be specified)
// Required: false
SubnetV4 string `url:"subnet_v4,omitempty" json:"subnet_v4,omitempty"`
// IPv6 subnet in CIDR notation (Either subnet_v4 or subnet_v6 must be specified)
// Required: false
SubnetV6 string `url:"subnet_v6,omitempty" json:"subnet_v6,omitempty"`
// Updated at
// Required: true
UpdatedAt time.Time `url:"updated_at" json:"updated_at" validate:"required"`
// ID of version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
}
type DHCPv4ConfigRequest struct {
// DNS
// Required: false
DNS []string `url:"dns,omitempty" json:"dns,omitempty"`
// Excluded address ranges
// Required: false
ExcludedAddressRanges []string `url:"excluded_address_ranges,omitempty" json:"excluded_address_ranges,omitempty"`
// Gateway
// Required: true
Gateway string `url:"gateway" json:"gateway" validate:"required"`
// ID of config
// Required: false
ID string `url:"id,omitempty" json:"id,omitempty"`
// Lease time
// Required: false
LeaseTime int64 `url:"lease_time,omitempty" json:"lease_time,omitempty"`
// Server IP
// Required: true
ServerIP string `url:"server_ip" json:"server_ip" validate:"required"`
// Server MAC
// Required: false
ServerMAC string `url:"server_mac,omitempty" json:"server_mac,omitempty"`
// Whether the config is enabled
// Required: true
Enabled bool `url:"enabled" json:"enabled"`
}
type DHCPv6ConfigRequest struct {
// Address prefix
// Required: true
AddressPrefix string `url:"address_prefix" json:"address_prefix" validate:"required"`
// DNS
// Required: false
DNS []string `url:"dns,omitempty" json:"dns,omitempty"`
// ID of config
// Required: false
ID string `url:"id,omitempty" json:"id,omitempty"`
// Lease time
// Required: true
LeaseTime int64 `url:"lease_time" json:"lease_time" validate:"required"`
// Server MAC
// Required: true
ServerMAC string `url:"server_mac" json:"server_mac" validate:"required"`
// Whether the config is enabled
// Required: true
Enabled bool `url:"enabled" json:"enabled"`
}
type EntityInfoRequest struct {
// User-friendly name for the entity
// Required: true
DisplayName string `url:"display_name" json:"display_name" validate:"required"`
// ID of entity
// Required: false
ID string `url:"id,omitempty" json:"id,omitempty"`
}
// Create creates extnet
func (e ExtNet) Create(ctx context.Context, req CreateRequest) (*ExternalNetworkResponse, error) {
err := validators.ValidateRequest(req)
if err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/external_network/create"
res, err := e.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
if err != nil {
return nil, err
}
info := ExternalNetworkResponse{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}

42
pkg/sdn/extnet/delete.go Normal file
View File

@@ -0,0 +1,42 @@
package extnet
import (
"context"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// DeleteRequest struct for delete extnet
type DeleteRequest struct {
// ID of external network
// Required: true
ExtNetID string `url:"external_network_id" json:"external_network_id" validate:"required"`
// ID of version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
// Force delete
// Required: false
Force bool `url:"force,omitempty" json:"force,omitempty"`
}
// Delete delete an external network
func (e ExtNet) Delete(ctx context.Context, req DeleteRequest) error {
err := validators.ValidateRequest(req)
if err != nil {
return validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/external_network/delete"
_, err = e.client.DecortApiCallCtype(ctx, http.MethodDelete, url, constants.MIMEJSON, req)
if err != nil {
return err
}
return nil
}

18
pkg/sdn/extnet/extnet.go Normal file
View File

@@ -0,0 +1,18 @@
// API Actor API for managing SDN external networks
package extnet
import (
"repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
)
// Structure for creating request to external networks
type ExtNet struct {
client interfaces.Caller
}
// Builder for external networks endpoints
func New(client interfaces.Caller) *ExtNet {
return &ExtNet{
client,
}
}

60
pkg/sdn/extnet/filter.go Normal file
View File

@@ -0,0 +1,60 @@
package extnet
// FilterByID returns ListExtNet with specified ID.
func (eList ListExtNet) FilterByID(id string) ListExtNet {
predicate := func(extNet ExternalNetworkResponse) bool {
return extNet.ID == id
}
return eList.FilterFunc(predicate)
}
// FilterByName returns ListExtNet with specified Bridge network name.
func (eList ListExtNet) FilterByName(name string) ListExtNet {
predicate := func(extNet ExternalNetworkResponse) bool {
return extNet.BridgeNetworkName == name
}
return eList.FilterFunc(predicate)
}
// FilterByIPv4 returns ListExtNet with specified default gateway IPv4.
func (eList ListExtNet) FilterByIPv4(IPv4 string) ListExtNet {
predicate := func(extNet ExternalNetworkResponse) bool {
return extNet.DefaultGatewayIPv4 == IPv4
}
return eList.FilterFunc(predicate)
}
// FilterByIPv6 returns ListExtNet with specified default gateway IPv6.
func (eList ListExtNet) FilterByIPv6(IPv6 string) ListExtNet {
predicate := func(extNet ExternalNetworkResponse) bool {
return extNet.DefaultGatewayIPv6 == IPv6
}
return eList.FilterFunc(predicate)
}
// FilterFunc allows filtering ListExtNet based on a user-specified predicate.
func (eList ListExtNet) FilterFunc(predicate func(response ExternalNetworkResponse) bool) ListExtNet {
var result ListExtNet
for _, item := range eList {
if predicate(item) {
result = append(result, item)
}
}
return result
}
// FindOne returns first element.
// If none was found, returns an empty struct.
func (eList ListExtNet) FindOne() ExternalNetworkResponse {
if len(eList) == 0 {
return ExternalNetworkResponse{}
}
return eList[0]
}

View File

@@ -0,0 +1,149 @@
package extnet
import (
"testing"
"time"
)
var testExtnetList = ListExtNet{
{
BridgeNetworkName: "br-ext-net-01",
DefaultGatewayIPv4: "192.168.1.1",
DefaultGatewayIPv6: "2001:db8::1",
Description: "test1",
Hypervisors: []string{
"hv-node-01",
},
ID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
VersionID: 1747141621952,
SubnetV4: "192.168.1.0/24",
SubnetV6: "2001:db8::/64",
CreatedAt: time.Date(2025, 10, 21, 20, 34, 30, 641000000, time.UTC),
UpdatedAt: time.Date(2025, 10, 21, 20, 34, 30, 641000000, time.UTC),
VLANTag: 100,
},
{
BridgeNetworkName: "br-backup-net-02",
DefaultGatewayIPv4: "10.0.1.1",
DefaultGatewayIPv6: "2001:db8:1::1",
Description: "test2",
Hypervisors: []string{
"hv-node-02",
},
ID: "b2c3d4e5-f6g7-8901-bcde-f23456789012",
VersionID: 1747141621953,
SubnetV4: "10.0.1.0/24",
SubnetV6: "2001:db8:1::/64",
CreatedAt: time.Date(2025, 10, 21, 20, 34, 30, 641000000, time.UTC),
UpdatedAt: time.Date(2025, 10, 21, 20, 34, 30, 641000000, time.UTC),
VLANTag: 200,
},
{
BridgeNetworkName: "br-test-net-03",
DefaultGatewayIPv4: "172.16.1.1",
DefaultGatewayIPv6: "2001:db8:2::1",
Description: "test3",
ExternalNetworkPorts: []ExternalNetworkPort{},
Hypervisors: []string{
"hv-node-05",
"hv-node-06",
},
ID: "c3d4e5f6-g7h8-9012-cdef-345678901234",
VersionID: 1747141621954,
SubnetV4: "172.16.1.0/24",
SubnetV6: "2001:db8:2::/64",
CreatedAt: time.Date(2025, 10, 21, 20, 34, 30, 641000000, time.UTC),
UpdatedAt: time.Date(2025, 10, 21, 20, 34, 30, 641000000, time.UTC),
VLANTag: 300,
},
}
func TestFilterByID(t *testing.T) {
actual := testExtnetList.FilterByID("a1b2c3d4-e5f6-7890-abcd-ef1234567890").FindOne()
if actual.ID != "a1b2c3d4-e5f6-7890-abcd-ef1234567890" {
t.Fatal("actual:", actual.ID, "> expected: a1b2c3d4-e5f6-7890-abcd-ef1234567890")
}
}
func TestFilterByName(t *testing.T) {
actual := testExtnetList.FilterByName("br-ext-net-01").FindOne()
if actual.BridgeNetworkName != "br-ext-net-01" {
t.Fatal("actual:", actual.BridgeNetworkName, ">> expected: br-ext-net-01")
}
}
func TestFilterByIPv4(t *testing.T) {
actual := testExtnetList.FilterByIPv4("192.168.1.1").FindOne()
if actual.DefaultGatewayIPv4 != "192.168.1.1" {
t.Fatal("actual:", actual.DefaultGatewayIPv4, ">> expected: 192.168.1.1")
}
}
func TestFilterByIPv6(t *testing.T) {
actual := testExtnetList.FilterByIPv6("2001:db8:1::1").FindOne()
if actual.DefaultGatewayIPv6 != "2001:db8:1::1" {
t.Fatal("actual:", actual.DefaultGatewayIPv6, ">> expected: 2001:db8:1::1")
}
}
func TestFilterFunc(t *testing.T) {
actual := testExtnetList.FilterFunc(func(response ExternalNetworkResponse) bool {
return response.BridgeNetworkName == "br-backup-net-02"
})
if len(actual) != 1 || actual[0].ID != "b2c3d4e5-f6g7-8901-bcde-f23456789012" {
t.Fatal("Expected 1 extnet with name 'br-backup-net-02', found:", len(actual))
}
}
func TestFindOneWithResults(t *testing.T) {
result := testExtnetList.FilterByID("c3d4e5f6-g7h8-9012-cdef-345678901234").FindOne()
if result.ID != "c3d4e5f6-g7h8-9012-cdef-345678901234" {
t.Fatal("Expected c3d4e5f6-g7h8-9012-cdef-345678901234, got:", result.ID)
}
}
func TestFindOneEmpty(t *testing.T) {
emptyList := ListExtNet{}
result := emptyList.FindOne()
if result.ID != "" || result.BridgeNetworkName != "" {
t.Fatal("Expected empty extNet, got:", result)
}
}
func TestFilterByIDNotFound(t *testing.T) {
actual := ListExtNet{}.FilterByID("nonexistent")
if len(actual) != 0 {
t.Fatal("Expected 0 extNet, found:", len(actual))
}
}
func TestFilterByNameNotFound(t *testing.T) {
actual := ListExtNet{}.FilterByName("Nonexistent")
if len(actual) != 0 {
t.Fatal("Expected 0 extNet, found:", len(actual))
}
}
func TestFilterByIPv4NotFound(t *testing.T) {
actual := testExtnetList.FilterByIPv4("nonexistent-ip")
if len(actual) != 0 {
t.Fatal("Expected 0 extNet, found:", len(actual))
}
}
func TestFilterByIPv6NotFound(t *testing.T) {
actual := testExtnetList.FilterByIPv6("nonexistent-ipv6")
if len(actual) != 0 {
t.Fatal("Expected 0 extNet, found:", len(actual))
}
}

50
pkg/sdn/extnet/get.go Normal file
View File

@@ -0,0 +1,50 @@
package extnet
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// GetRequest struct to get information about external network
type GetRequest struct {
// ID of external network
// Required: false
ExtNetID string `url:"external_network_id" json:"external_network_id" validate:"required"`
// ID of access group
// Required: false
AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,omitempty"`
}
// Get gets external network details as a ExternalNetworkResponse struct
func (e ExtNet) Get(ctx context.Context, req GetRequest) (*ExternalNetworkResponse, error) {
res, err := e.GetRaw(ctx, req)
if err != nil {
return nil, err
}
info := ExternalNetworkResponse{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}
// GetRaw gets external network details as an array of bytes
func (e ExtNet) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
if err := validators.ValidateRequest(req); err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/external_network/get"
res, err := e.client.DecortApiCall(ctx, http.MethodGet, url, req)
return res, err
}

10
pkg/sdn/extnet/ids.go Normal file
View File

@@ -0,0 +1,10 @@
package extnet
// IDs gets array of IDs from ListExtNet struct
func (eList ListExtNet) IDs() []string {
res := make([]string, 0, len(eList))
for _, item := range eList {
res = append(res, item.ID)
}
return res
}

114
pkg/sdn/extnet/list.go Normal file
View File

@@ -0,0 +1,114 @@
package extnet
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// ListRequest struct to get a list of external networks
type ListRequest struct {
// Filter by access group ID
// Required: false
AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,omitempty"`
// Filter by display name
// Required: false
DisplayName string `url:"display_name,omitempty" json:"display_name,omitempty"`
// Filter by IP version (v4 or v6)
// Required: false
Subnet string `url:"subnet,omitempty" json:"subnet,omitempty" validate:"omitempty,ipTypes"`
// Filter by IPv4 subnet (CIDR notation)
// Required: false
SubnetV4 string `url:"subnet_v4,omitempty" json:"subnet_v4,omitempty"`
// Filter by IPv4 subnet (CIDR notation)
// Required: false
SubnetV6 string `url:"subnet_v6,omitempty" json:"subnet_v6,omitempty"`
// Filter by exact bridge network name
// Required: false
BridgeNetworkName string `url:"bridge_network_name,omitempty" json:"bridge_network_name,omitempty"`
// Filter by VLAN tag
// Required: false
VLANTag string `url:"vlan_tag,omitempty" json:"vlan_tag,omitempty"`
// Filter by IPv4 default gateway
// Required: false
DefaultGatewayIPv4 string `url:"default_gateway_ipv4,omitempty" json:"default_gateway_ipv4,omitempty"`
// Filter by IPv6 default gateway
// Required: false
DefaultGatewayIPv6 string `url:"default_gateway_ipv6,omitempty" json:"default_gateway_ipv6,omitempty"`
// Filter by enabled status
// Required: false
Enabled interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
// Filter by update date from
// Required: false
UpdatedFrom string `url:"updated_from,omitempty" json:"updated_from,omitempty"`
// Filter lby update date to
// Required: false
UpdatedTo string `url:"updated_to,omitempty" json:"updated_to,omitempty"`
// Filter by create date from
// Required: false
CreatedFrom string `url:"created_from,omitempty" json:"created_from,omitempty"`
// Filter lby create date to
// Required: false
CreatedTo string `url:"created_to,omitempty" json:"created_to,omitempty"`
// Page number for pagination
// Required: false
Page uint64 `url:"page,omitempty" json:"page,omitempty"`
// Number of results per page
// Required: false
PerPage uint64 `url:"per_page,omitempty" json:"per_page,omitempty"`
// Field to sort by (display_name, created_at, updated_at, deleted_at, etc)
// Required: false
SortBy string `url:"sort_by,omitempty" json:"sort_by,omitempty"`
// Sort order (asc/desc)
// Required: false
SortOrder string `url:"sort_order,omitempty" json:"sort_order,omitempty"`
}
// List gets list of all available external networks as a ListExtNet struct
func (e ExtNet) List(ctx context.Context, req ListRequest) (ListExtNet, error) {
res, err := e.ListRaw(ctx, req)
if err != nil {
return nil, err
}
list := ListExtNet{}
err = json.Unmarshal(res, &list)
if err != nil {
return nil, err
}
return list, nil
}
// ListRaw gets list of all available external networks as an array of bytes
func (e ExtNet) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
if err := validators.ValidateRequest(req); err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/external_network/list"
res, err := e.client.DecortApiCall(ctx, http.MethodGet, url, req)
return res, err
}

578
pkg/sdn/extnet/models.go Normal file
View File

@@ -0,0 +1,578 @@
package extnet
import "time"
// List external networks
type ListExtNet []ExternalNetworkResponse
type ExternalNetworkResponse struct {
// Name of the bridge network
BridgeNetworkName string `json:"bridge_network_name"`
// IPv4 default gateway address
DefaultGatewayIPv4 string `json:"default_gateway_ipv4"`
// IPv6 default gateway address
DefaultGatewayIPv6 string `json:"default_gateway_ipv6"`
// Detailed description of the external network
Description string `json:"description"`
// List of external network ports
ExternalNetworkPorts []ExternalNetworkPort `json:"external_network_ports"`
// List of hypervisor names
Hypervisors []string `json:"hypervisors"`
// Extnet ID
ID string `json:"id"`
// Status
Status Status `json:"status"`
// ID of version
VersionID uint64 `json:"version_id"`
// IPv4 subnet in CIDR notation
SubnetV4 string `json:"subnet_v4"`
// IPv6 subnet in CIDR notation
SubnetV6 string `json:"subnet_v6"`
// Creation time
CreatedAt time.Time `json:"created_at"`
// Update time
UpdatedAt time.Time `json:"updated_at"`
// VLAN tag identifier
VLANTag int64 `json:"vlan_tag"`
}
type ExternalNetworkPort struct {
// Access group ID
AccessGroupID string `json:"access_group_id"`
// Access group name
AccessGroupName string `json:"access_group_name"`
// Comment for the external network port
Comment string `json:"comment"`
// User-friendly name for the external network port
DisplayName string `json:"display_name"`
// Whether the network port is enabled
Enabled bool `json:"enabled"`
// IPv4
IPv4 string `json:"ipv4"`
// IPv6
IPv6 string `json:"ipv6"`
// IPv6 Config
IPv6Config IPv6Config `json:"ipv6_config"`
// MAC address
MAC string `json:"mac"`
// Router gateway port
RouterGatewayPort RouterGatewayPort `json:"router_gateway_port"`
// Floating IP
FloatingIP FloatingIP `json:"floating_ip"`
}
type IPv6Config struct {
// Address mode
AddressMode string `json:"address_mode"`
// If true, the port will periodically send RA packets.
EnablePeriodicRa bool `json:"enable_periodic_ra"`
// The number of waiting seconds between sending periodic RA
IntervalRa int64 `json:"interval_ra"`
// The Default Router Preference (PRF) indicates whether this router should be preferred over other default routers.
RouterPreference string `json:"router_preference"`
}
type RouterGatewayPort struct {
// Creation time
CreatedAt time.Time `json:"created_at"`
// Description
Description string `json:"description"`
// Port ID
ID string `json:"id"`
// User-friendly name for the external network port
RouterDisplayName string `json:"router_display_name"`
// Router ID
RouterID string `json:"router_id"`
// SNAT Enabled
SNATEnabled bool `json:"snat_enabled"`
// Update time
UpdatedAt time.Time `json:"updated_at"`
}
type FloatingIP struct {
// Access group ID
AccessGroupID string `json:"access_group_id"`
// Access group name
AccessGroupName string `json:"access_group_name"`
// Created time
CreatedAt time.Time `json:"created_at"`
// External network port
ExternalNetworkPort string `json:"external_network_port"`
// ID of floating IP
ID string `json:"id"`
// Logical Port
LogicalPort LogicalPort `json:"logical_port"`
// Router
Router Router `json:"router"`
// Update time
UpdatedAt time.Time `json:"updated_at"`
// ID of version
VersionID uint64 `json:"version_id"`
}
type LogicalPort struct {
// Logical Port ID
ID string `json:"id"`
// Access group ID
AccessGroupID string `json:"access_group_id"`
// Access group name
AccessGroupName string `json:"access_group_name"`
// MAC of adapter
AdapterMAC string `json:"adapter_mac"`
// Address detection
AddressDetection bool `json:"address_detection"`
// Description
Description string `json:"description"`
// Created time
CreatedAt time.Time `json:"created_at"`
// User-friendly name for router
DisplayName string `json:"display_name"`
// Whether the logical port is enabled
Enabled bool `json:"enabled"`
// External Network ID
ExternalNetworkID string `json:"external_network_id"`
// Hypervisor name
Hypervisor string `json:"hypervisor"`
// User-friendly name for hypervisor
HypervisorDisplayName string `json:"hypervisor_display_name"`
// Live Migration Target Hv
LiveMigrationTargetHV string `json:"live_migration_target_hv"`
// Status
Status Status `json:"status"`
// Bindings struct
Bindings PortBindings `json:"bindings"`
// Unique Identifier
UniqueIDentifier string `json:"unique_identifier"`
// Updated time
UpdatedAt time.Time `json:"updated_at"`
// ID of version
VersionID uint64 `json:"version_id"`
}
type PortBindings struct {
// Binding ID
ID string `json:"id"`
// User-friendly name for segment
SegmentDisplayName string `json:"segment_display_name"`
// Segment ID
SegmentID string `json:"segment_id"`
// Port security
PortSecurity bool `json:"port_security"`
// Address detection
AddressDetection bool `json:"address_detection"`
// Is Exclude From Firewall
IsExcludedFromFirewall bool `json:"is_excluded_from_firewall"`
// ID of version
VersionID uint64 `json:"version_id"`
// Created time
CreatedAt time.Time `json:"created_at"`
// Update time
UpdatedAt time.Time `json:"updated_at"`
// Logical port addresses
LogicalPortAddresses []LogicalPortAddress `json:"logical_port_addresses"`
}
type LogicalPortAddress struct {
// IP of port
IP string `json:"ip"`
// IP type (IPv4 or IPv6)
IPType string `json:"ip_type"`
// Is discovered
IsDiscovered bool `json:"is_discovered"`
// Is primary
IsPrimary bool `json:"is_primary"`
// MAC
MAC string `json:"mac"`
// ID
ID string `json:"id"`
// Logical port id
LogicalPortID string `json:"logical_port_id"`
// Assigned time
AssignedAt time.Time `json:"assigned_at"`
}
type Router struct {
// Access group ID
AccessGroupID string `json:"access_group_id"`
// Access group name
AccessGroupName string `json:"access_group_name"`
// Created time
CreatedAt time.Time `json:"created_at"`
// Detailed description of the router
Description string `json:"description"`
// User-friendly name for the router
DisplayName string `json:"display_name"`
// Whether the router is enabled
Enabled bool `json:"enabled"`
// Gateway ports
GatewayPorts []GatewayPort `json:"gateway_ports"`
// ID of router
ID string `json:"id"`
// Policies
Policies []RouterPolicy `json:"policies"`
// Ports
Ports []RouterPort `json:"ports"`
// Status
Status Status `json:"status"`
// Update time
UpdatedAt time.Time `json:"updated_at"`
// ID of version
VersionID uint64 `json:"version_id"`
}
type GatewayPort struct {
// Created time
CreatedAt time.Time `json:"created_at"`
// Description
Description string `json:"description"`
// L4 port max
ExternalL4PortMax int64 `json:"external_l4_port_max"`
// L4 port min
ExternalL4PortMin int64 `json:"external_l4_port_min"`
// External network port
ExternalNetworkPort interface{} `json:"external_network_port"`
// ID of port
ID string `json:"id"`
// SNAT Enabled
SNATEnabled bool `json:"snat_enabled"`
// Status
Status Status `json:"status"`
// Update time
UpdatedAt time.Time `json:"updated_at"`
// ID of version
VersionID uint64 `json:"version_id"`
}
type RouterPolicy struct {
// Action
Action string `json:"action"`
// Created time
CreatedAt time.Time `json:"created_at"`
// User-friendly name for the policy
DisplayName string `json:"display_name"`
// Whether the policy is enabled
Enabled bool `json:"enabled"`
// ID of router policy
ID string `json:"id"`
// Match
Match interface{} `json:"match"`
// Next IPv4 address
NextIPv4Address []string `json:"next_ipv4_address"`
// Next IPv6 address
NextIPv6Address []string `json:"next_ipv6_address"`
// Priority number
Priority int64 `json:"priority"`
// Update time
UpdatedAt time.Time `json:"updated_at"`
// ID of version
VersionID uint64 `json:"version_id"`
}
type RouterPort struct {
// Created at
CreatedAt time.Time `json:"created_at"`
// Detailed description of the router port
Description string `json:"description"`
// Whether the router port is enabled
Enabled bool `json:"enabled"`
// ID of router port
ID string `json:"id"`
// Next IPv4 address
IPv4Address string `json:"ipv4_address"`
// Next IPv6 address
IPv6Address string `json:"ipv6_address"`
// IPv6 Config
IPv6Config IPv6Config `json:"ipv6_config"`
// MAC address
MAC string `json:"mac"`
// Segment
Segment Segment `json:"segment"`
// Segment ID
SegmentID string `json:"segment_id"`
// Status
Status Status `json:"status"`
// Update time
UpdatedAt time.Time `json:"updated_at"`
// ID of version
VersionID uint64 `json:"version_id"`
}
type Segment struct {
// Access group ID
AccessGroupID string `json:"access_group_id"`
// Access group name
AccessGroupName string `json:"access_group_name"`
// Created time
CreatedAt time.Time `json:"created_at"`
// Detailed description of the router port
Description string `json:"description"`
// DHCP IPv4
DHCPv4 DHCPv4Config `json:"dhcp_v4"`
// DHCP IPv6
DHCPv6 DHCPv6Config `json:"dhcp_v6"`
// User-friendly name for the segment
DisplayName string `json:"display_name"`
// Whether the segment is enabled
Enabled bool `json:"enabled"`
// ID of segment
ID string `json:"id"`
// Logical ports info
LogicalPortsInfo []EntityInfo `json:"logical_ports_info"`
// Routers info
RoutersInfo []EntityInfo `json:"routers_info"`
// Status
Status Status `json:"status"`
// IPv4 subnet in CIDR notation
SubnetV4 string `json:"subnet_v4"`
// IPv6 subnet in CIDR notation
SubnetV6 string `json:"subnet_v6"`
// Update time
UpdatedAt time.Time `json:"updated_at"`
// ID of version
VersionID uint64 `json:"version_id"`
}
type DHCPv4Config struct {
// DNS
DNS []string `json:"dns"`
// Excluded address ranges
ExcludedAddressRanges []string `json:"excluded_address_ranges"`
// Gateway
Gateway string `json:"gateway"`
// ID of config
ID string `json:"id"`
// Lease time
LeaseTime int64 `json:"lease_time"`
// Server IP
ServerIP string `json:"server_ip"`
// Server MAC
ServerMAC string `json:"server_mac"`
// Whether the config is enabled
Enabled bool `json:"enabled"`
}
type DHCPv6Config struct {
// Address prefix
AddressPrefix string `json:"address_prefix"`
// DNS
DNS []string `json:"dns"`
// ID of config
ID string `json:"id"`
// Lease time
LeaseTime int64 `json:"lease_time"`
// Server MAC
ServerMAC string `json:"server_mac"`
// Whether the config is enabled
Enabled bool `json:"enabled"`
}
type EntityInfo struct {
// User-friendly name for the entity
DisplayName string `json:"display_name"`
// ID of entity
ID string `json:"id"`
}
type Status struct {
// Common
Common string `json:"common"`
// Hypervisors status
Hypervisors []HypervisorStatus `json:"hypervisors"`
}
type HypervisorStatus struct {
// Status
Status string `json:"status"`
// Name of hypervisor
Name string `json:"name"`
// User-friendly name for the hypervisor
DisplayName string `json:"display_name"`
// Hypervisor status
HypervisorStatus string `json:"hypervisor_status"`
// Synced time
SyncedAt time.Time `json:"synced_at"`
}
type ExternalNetworkAddPortsResponce struct {
// ID of extnet port
ID string `json:"id"`
// ID of extnet
ExtNetID string `json:"external_network_id"`
// User-friendly name for the external network port
DisplayName string `json:"display_name"`
// Comment
Comment string `json:"comment"`
// Mac
MAC string `json:"mac"`
// IPv4 gateway address
IPv4 string `json:"ipv4"`
// IPv6 gateway address
IPv6 string `json:"ipv6"`
// IPv6 config
IPv6Config IPv6Config `json:"ipv6_config"`
// Whether the network port is enabled
Enabled bool `json:"enabled"`
// ID of version
VersionID uint64 `json:"version_id"`
// Router gateway port
RouterGatewayPort RouterGatewayPort `json:"router_gateway_port"`
}

View File

@@ -0,0 +1,77 @@
package extnet
import (
"context"
"encoding/json"
"net/http"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
"repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
)
// PortAddRequest struct for add port to extnet
type PortAddRequest struct {
// ID of external network
// Required: true
ExtNetID string `url:"external_network_id" json:"external_network_id" validate:"required"`
// Access group ID
// Required: true
AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
// ID of version
// Required: true
VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
// Description of the port addition operation
// Required: true
Comment string `url:"comment" json:"comment" validate:"required"`
// User-friendly name for the external network
// Required: true
DisplayName string `url:"display_name" json:"display_name" validate:"required"`
// Whether the network is enabled
// Required: true
Enabled bool `url:"enabled" json:"enabled"`
// IPv4
// Required: false
IPv4 string `url:"ipv4,omitempty" json:"ipv4,omitempty"`
// IPv6
// Required: false
IPv6 string `url:"ipv6,omitempty" json:"ipv6,omitempty"`
// IPv6 Config
// Required: false
IPv6Config *IPv6ConfigRequest `url:"-" json:"ipv6_config,omitempty"`
// MAC address
// Required: false
MAC string `url:"mac,omitempty" json:"mac,omitempty"`
}
// AddPort added a port an external network
func (e ExtNet) AddPort(ctx context.Context, req PortAddRequest) (*ExternalNetworkAddPortsResponce, error) {
err := validators.ValidateRequest(req)
if err != nil {
return nil, validators.ValidationErrors(validators.GetErrors(err))
}
url := "/sdn/external_network/port_add"
res, err := e.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
if err != nil {
return nil, err
}
info := ExternalNetworkAddPortsResponce{}
err = json.Unmarshal(res, &info)
if err != nil {
return nil, err
}
return &info, nil
}

Some files were not shown because too many files have changed in this diff Show More