diff --git a/CHANGELOG.md b/CHANGELOG.md
index f114752..4797133 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,12 +1,73 @@
-## Version 1.12.8
+## Version 1.12.9
Методы `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()` находятся в альфа-версии.
+
+### Добавлено
+
+#### address pool
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BGOS-515 | Группа методов address pool в sdn |
+
+#### compute
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BGOS-650 | Поля `BusNumber`, `EnableSecGroups`, `LibvirtSettings`, `MTU`, `NodeID`, `SDNInterfaceID`, `SecurityGroups`, `TrunkTags` в структуру ответа `RecordNetAttach` в compute/cloudapi и compute/cloudbroker |
+
+#### default security policies
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BGOS-516 | Группа методов default security policies в sdn |
+
+#### image
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BGOS-648 | Поле `ToClean` в структурах ответа `RecordImage` в cloudapi/image и `RecordImage`, `ItemImage` cloudbroker/image |
+
+#### network object groups
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| Идентификатор
задачи | Описание |
+| BGOS-520 | Группа методов network object groups в sdn |
+
+#### logical ports
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BGOS-519 | Группа методов logical ports в sdn |
+
+#### security policies
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BGOS-521 | Группа методов security policies в sdn |
+
+#### sdn external networks
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BGOS-517 | Группа методов external networks в sdn |
+
+#### sdn floating ips
+| Идентификатор
задачи | Описание |
+|--- | --- |
+| BGOS-594 | Группа методов floating ips в sdn |
+
+#### sdn routers
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BGOS-524 | Группа методов external routers в sdn |
+
+#### sdn segments
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BGOS-523 | Группа методов segments в sdn |
+
### Исправлено
-#### Общие изменения
+#### kvmx86
| Идентификатор
задачи | Описание |
| --- | --- |
-| BGOS-644, BGOS-645, BGOS-646 | Исправлены уязвимости, обновлена версия go до v1.24.0 |
\ No newline at end of file
+| BGOS-652 | Структура `Interface` заменена на `InterfaceMassCreate` в структуре `MassCreateRequest` в cloudbroker/kvmx86 |
+
diff --git a/README.md b/README.md
index b9ac72a..29c59a5 100644
--- a/README.md
+++ b/README.md
@@ -27,6 +27,7 @@ Decort SDK - это библиотека, написанная на языке G
- [Список API](#список-api)
- [Cloudapi](#cloudapi)
- [Cloudbroker](#cloudbroker)
+ - [SDN](#sdn)
- [Работа с библиотекой](#работа-с-библиотекой)
- [Настройка конфигурации клиента](#настройка-конфигурации-клиента)
- [Пример конфигурации клиента](#пример-конфигурации-клиента)
@@ -174,7 +175,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 +377,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 +589,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. Вызвать метод, отвечающий за выполнение запроса и передать в него:
diff --git a/client.go b/client.go
index d68e035..ff84226 100644
--- a/client.go
+++ b/client.go
@@ -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
}
diff --git a/client_bvs.go b/client_bvs.go
index 31c2cb1..05f353e 100644
--- a/client_bvs.go
+++ b/client_bvs.go
@@ -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
diff --git a/internal/validators/custom.go b/internal/validators/custom.go
index 13a0ace..287535d 100644
--- a/internal/validators/custom.go
+++ b/internal/validators/custom.go
@@ -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)
+}
diff --git a/internal/validators/messages.go b/internal/validators/messages.go
index 09d1a77..3ea36a1 100644
--- a/internal/validators/messages.go
+++ b/internal/validators/messages.go
@@ -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()
diff --git a/internal/validators/validator.go b/internal/validators/validator.go
index 9a21cc6..330107f 100644
--- a/internal/validators/validator.go
+++ b/internal/validators/validator.go
@@ -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
}
diff --git a/internal/validators/values.go b/internal/validators/values.go
index 5e1802e..f481329 100644
--- a/internal/validators/values.go
+++ b/internal/validators/values.go
@@ -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 (
diff --git a/legacy-client.go b/legacy-client.go
index 9103e1a..64ed0e5 100644
--- a/legacy-client.go
+++ b/legacy-client.go
@@ -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
diff --git a/pkg/cloudapi/compute/models.go b/pkg/cloudapi/compute/models.go
index 66f65a5..7847772 100644
--- a/pkg/cloudapi/compute/models.go
+++ b/pkg/cloudapi/compute/models.go
@@ -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
diff --git a/pkg/cloudapi/image/models.go b/pkg/cloudapi/image/models.go
index d71f0df..c348cd2 100644
--- a/pkg/cloudapi/image/models.go
+++ b/pkg/cloudapi/image/models.go
@@ -200,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"`
diff --git a/pkg/cloudbroker/compute/models.go b/pkg/cloudbroker/compute/models.go
index 6fdf3b4..fd83f21 100644
--- a/pkg/cloudbroker/compute/models.go
+++ b/pkg/cloudbroker/compute/models.go
@@ -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"`
}
diff --git a/pkg/cloudbroker/image/models.go b/pkg/cloudbroker/image/models.go
index c094a9d..8619c09 100644
--- a/pkg/cloudbroker/image/models.go
+++ b/pkg/cloudbroker/image/models.go
@@ -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"`
diff --git a/pkg/cloudbroker/kvmx86/create.go b/pkg/cloudbroker/kvmx86/create.go
index 5f3e537..614cf31 100644
--- a/pkg/cloudbroker/kvmx86/create.go
+++ b/pkg/cloudbroker/kvmx86/create.go
@@ -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
diff --git a/pkg/cloudbroker/kvmx86/mass_create.go b/pkg/cloudbroker/kvmx86/mass_create.go
index 1a28074..d0850ee 100644
--- a/pkg/cloudbroker/kvmx86/mass_create.go
+++ b/pkg/cloudbroker/kvmx86/mass_create.go
@@ -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
diff --git a/pkg/sdn/acsgroups/delete.go b/pkg/sdn/acsgroups/delete.go
index 7915e9f..6ce09b5 100644
--- a/pkg/sdn/acsgroups/delete.go
+++ b/pkg/sdn/acsgroups/delete.go
@@ -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"`
}
diff --git a/pkg/sdn/acsgroups/get.go b/pkg/sdn/acsgroups/get.go
new file mode 100644
index 0000000..6a3089b
--- /dev/null
+++ b/pkg/sdn/acsgroups/get.go
@@ -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
+}
diff --git a/pkg/sdn/acsgroups/path.go b/pkg/sdn/acsgroups/update.go
similarity index 83%
rename from pkg/sdn/acsgroups/path.go
rename to pkg/sdn/acsgroups/update.go
index d0318cd..d15b0fe 100644
--- a/pkg/sdn/acsgroups/path.go
+++ b/pkg/sdn/acsgroups/update.go
@@ -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 {
diff --git a/pkg/sdn/address_pools.go b/pkg/sdn/address_pools.go
new file mode 100644
index 0000000..49fe33b
--- /dev/null
+++ b/pkg/sdn/address_pools.go
@@ -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)
+}
diff --git a/pkg/sdn/adrspools/adress_pools.go b/pkg/sdn/adrspools/adress_pools.go
new file mode 100644
index 0000000..78cedfc
--- /dev/null
+++ b/pkg/sdn/adrspools/adress_pools.go
@@ -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,
+ }
+}
diff --git a/pkg/sdn/adrspools/create.go b/pkg/sdn/adrspools/create.go
new file mode 100644
index 0000000..380a3d3
--- /dev/null
+++ b/pkg/sdn/adrspools/create.go
@@ -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
+}
diff --git a/pkg/sdn/adrspools/delete.go b/pkg/sdn/adrspools/delete.go
new file mode 100644
index 0000000..f4c3e11
--- /dev/null
+++ b/pkg/sdn/adrspools/delete.go
@@ -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
+}
diff --git a/pkg/sdn/adrspools/filter.go b/pkg/sdn/adrspools/filter.go
new file mode 100644
index 0000000..6c64333
--- /dev/null
+++ b/pkg/sdn/adrspools/filter.go
@@ -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]
+}
diff --git a/pkg/sdn/adrspools/filter_test.go b/pkg/sdn/adrspools/filter_test.go
new file mode 100644
index 0000000..813f749
--- /dev/null
+++ b/pkg/sdn/adrspools/filter_test.go
@@ -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))
+ }
+}
diff --git a/pkg/sdn/adrspools/get.go b/pkg/sdn/adrspools/get.go
new file mode 100644
index 0000000..287938b
--- /dev/null
+++ b/pkg/sdn/adrspools/get.go
@@ -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
+}
diff --git a/pkg/sdn/adrspools/ids.go b/pkg/sdn/adrspools/ids.go
new file mode 100644
index 0000000..92c19a7
--- /dev/null
+++ b/pkg/sdn/adrspools/ids.go
@@ -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
+}
diff --git a/pkg/sdn/adrspools/list.go b/pkg/sdn/adrspools/list.go
new file mode 100644
index 0000000..be4630a
--- /dev/null
+++ b/pkg/sdn/adrspools/list.go
@@ -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
+}
diff --git a/pkg/sdn/adrspools/models.go b/pkg/sdn/adrspools/models.go
new file mode 100644
index 0000000..97d9508
--- /dev/null
+++ b/pkg/sdn/adrspools/models.go
@@ -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"`
+}
diff --git a/pkg/sdn/adrspools/serialize.go b/pkg/sdn/adrspools/serialize.go
new file mode 100644
index 0000000..ce19e0d
--- /dev/null
+++ b/pkg/sdn/adrspools/serialize.go
@@ -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)
+}
diff --git a/pkg/sdn/adrspools/update.go b/pkg/sdn/adrspools/update.go
new file mode 100644
index 0000000..573b710
--- /dev/null
+++ b/pkg/sdn/adrspools/update.go
@@ -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
+}
diff --git a/pkg/sdn/default_security_policies.go b/pkg/sdn/default_security_policies.go
new file mode 100644
index 0000000..fc7159f
--- /dev/null
+++ b/pkg/sdn/default_security_policies.go
@@ -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)
+}
diff --git a/pkg/sdn/defsecpolicies/default_security_policies.go b/pkg/sdn/defsecpolicies/default_security_policies.go
new file mode 100644
index 0000000..4c042fe
--- /dev/null
+++ b/pkg/sdn/defsecpolicies/default_security_policies.go
@@ -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,
+ }
+}
diff --git a/pkg/sdn/defsecpolicies/filter.go b/pkg/sdn/defsecpolicies/filter.go
new file mode 100644
index 0000000..d094997
--- /dev/null
+++ b/pkg/sdn/defsecpolicies/filter.go
@@ -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]
+}
diff --git a/pkg/sdn/defsecpolicies/filter_test.go b/pkg/sdn/defsecpolicies/filter_test.go
new file mode 100644
index 0000000..df89ba7
--- /dev/null
+++ b/pkg/sdn/defsecpolicies/filter_test.go
@@ -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))
+ }
+}
diff --git a/pkg/sdn/defsecpolicies/ids.go b/pkg/sdn/defsecpolicies/ids.go
new file mode 100644
index 0000000..ff767e5
--- /dev/null
+++ b/pkg/sdn/defsecpolicies/ids.go
@@ -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
+}
diff --git a/pkg/sdn/defsecpolicies/list.go b/pkg/sdn/defsecpolicies/list.go
new file mode 100644
index 0000000..5db7ad9
--- /dev/null
+++ b/pkg/sdn/defsecpolicies/list.go
@@ -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
+}
diff --git a/pkg/sdn/defsecpolicies/models.go b/pkg/sdn/defsecpolicies/models.go
new file mode 100644
index 0000000..c356562
--- /dev/null
+++ b/pkg/sdn/defsecpolicies/models.go
@@ -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"`
+}
diff --git a/pkg/sdn/defsecpolicies/serialize.go b/pkg/sdn/defsecpolicies/serialize.go
new file mode 100644
index 0000000..70b59df
--- /dev/null
+++ b/pkg/sdn/defsecpolicies/serialize.go
@@ -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)
+}
diff --git a/pkg/sdn/defsecpolicies/update.go b/pkg/sdn/defsecpolicies/update.go
new file mode 100644
index 0000000..54371ad
--- /dev/null
+++ b/pkg/sdn/defsecpolicies/update.go
@@ -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
+}
diff --git a/pkg/sdn/extnet.go b/pkg/sdn/extnet.go
new file mode 100644
index 0000000..aec6d39
--- /dev/null
+++ b/pkg/sdn/extnet.go
@@ -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)
+}
diff --git a/pkg/sdn/extnet/create.go b/pkg/sdn/extnet/create.go
new file mode 100644
index 0000000..1813a65
--- /dev/null
+++ b/pkg/sdn/extnet/create.go
@@ -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
+}
diff --git a/pkg/sdn/extnet/delete.go b/pkg/sdn/extnet/delete.go
new file mode 100644
index 0000000..ed5210f
--- /dev/null
+++ b/pkg/sdn/extnet/delete.go
@@ -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
+}
diff --git a/pkg/sdn/extnet/extnet.go b/pkg/sdn/extnet/extnet.go
new file mode 100644
index 0000000..7446cac
--- /dev/null
+++ b/pkg/sdn/extnet/extnet.go
@@ -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,
+ }
+}
diff --git a/pkg/sdn/extnet/filter.go b/pkg/sdn/extnet/filter.go
new file mode 100644
index 0000000..30a79ad
--- /dev/null
+++ b/pkg/sdn/extnet/filter.go
@@ -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]
+}
diff --git a/pkg/sdn/extnet/filter_test.go b/pkg/sdn/extnet/filter_test.go
new file mode 100644
index 0000000..f542776
--- /dev/null
+++ b/pkg/sdn/extnet/filter_test.go
@@ -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))
+ }
+}
diff --git a/pkg/sdn/extnet/get.go b/pkg/sdn/extnet/get.go
new file mode 100644
index 0000000..a7ff6a4
--- /dev/null
+++ b/pkg/sdn/extnet/get.go
@@ -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
+}
diff --git a/pkg/sdn/extnet/ids.go b/pkg/sdn/extnet/ids.go
new file mode 100644
index 0000000..7745499
--- /dev/null
+++ b/pkg/sdn/extnet/ids.go
@@ -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
+}
diff --git a/pkg/sdn/extnet/list.go b/pkg/sdn/extnet/list.go
new file mode 100644
index 0000000..591c37b
--- /dev/null
+++ b/pkg/sdn/extnet/list.go
@@ -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
+}
diff --git a/pkg/sdn/extnet/models.go b/pkg/sdn/extnet/models.go
new file mode 100644
index 0000000..2886442
--- /dev/null
+++ b/pkg/sdn/extnet/models.go
@@ -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"`
+}
diff --git a/pkg/sdn/extnet/port_add.go b/pkg/sdn/extnet/port_add.go
new file mode 100644
index 0000000..40f58fa
--- /dev/null
+++ b/pkg/sdn/extnet/port_add.go
@@ -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
+}
diff --git a/pkg/sdn/extnet/port_update.go b/pkg/sdn/extnet/port_update.go
new file mode 100644
index 0000000..dcf4b41
--- /dev/null
+++ b/pkg/sdn/extnet/port_update.go
@@ -0,0 +1,85 @@
+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"
+)
+
+// PortUpdateRequest struct for update port to extnet
+type PortUpdateRequest 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"`
+
+ // Port ID
+ // Required: true
+ PortID string `url:"port_id" json:"port_id" validate:"required"`
+
+ // Port version ID
+ // Required: true
+ PortVersionID uint64 `url:"port_version_id" json:"port_version_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"`
+}
+
+// UpdatePort updated a port an external network
+func (e ExtNet) UpdatePort(ctx context.Context, req PortUpdateRequest) (*ExternalNetworkResponse, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/external_network/port_update"
+
+ res, err := e.client.DecortApiCallCtype(ctx, http.MethodPut, 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
+}
diff --git a/pkg/sdn/extnet/update.go b/pkg/sdn/extnet/update.go
new file mode 100644
index 0000000..695b8db
--- /dev/null
+++ b/pkg/sdn/extnet/update.go
@@ -0,0 +1,89 @@
+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"
+)
+
+// UpdateRequest struct for update extnet
+type UpdateRequest 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"`
+
+ // Bridge network name
+ // 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"`
+
+ // List of hypervisor names
+ // Required: true
+ Hypervisors []string `url:"hypervisors" json:"hypervisors" validate:"required"`
+
+ // Access group ID
+ // Required: false
+ AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,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"`
+
+ // Whether the network is enabled
+ // Required: true
+ Enabled bool `url:"enabled" json:"enabled"`
+
+ // 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"`
+}
+
+// Update updated an external network
+func (e ExtNet) Update(ctx context.Context, req UpdateRequest) (*ExternalNetworkResponse, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/external_network/update"
+
+ res, err := e.client.DecortApiCallCtype(ctx, http.MethodPut, 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
+}
diff --git a/pkg/sdn/flips.go b/pkg/sdn/flips.go
new file mode 100644
index 0000000..3c235ed
--- /dev/null
+++ b/pkg/sdn/flips.go
@@ -0,0 +1,10 @@
+package sdn
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/flips"
+)
+
+// Accessing the SDN method group
+func (sdn *SDN) FloatingIPs() *flips.FloatingIPs {
+ return flips.New(sdn.client)
+}
diff --git a/pkg/sdn/flips/create.go b/pkg/sdn/flips/create.go
new file mode 100644
index 0000000..e0995e4
--- /dev/null
+++ b/pkg/sdn/flips/create.go
@@ -0,0 +1,52 @@
+package flips
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+type CreateRequest struct {
+ // Access Group ID
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // ID of an external network port
+ // Required: true
+ ExtNetPortID string `url:"external_network_port_id" json:"external_network_port_id" validate:"required"`
+
+ // ID of a logical network port
+ // Required: true
+ LogicalPortID string `url:"logical_port_id" json:"logical_port_id" validate:"required"`
+
+ // ID of a router
+ // Required: true
+ RouterID string `url:"router_id" json:"router_id" validate:"required"`
+}
+
+// Create creates a floating ip
+func (fi FloatingIPs) Create(ctx context.Context, req CreateRequest) (*RecordFloatingIP, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/floating_ip/create"
+
+ res, err := fi.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RecordFloatingIP{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/flips/delete.go b/pkg/sdn/flips/delete.go
new file mode 100644
index 0000000..6ecb7cf
--- /dev/null
+++ b/pkg/sdn/flips/delete.go
@@ -0,0 +1,51 @@
+package flips
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// DeleteRequest to delete a floating ip
+type DeleteRequest struct {
+ // ID of a floating IP
+ // Required: true
+ FloatingIPID string `url:"floating_ip_id" json:"floating_ip_id" validate:"required"`
+
+ // Version ID
+ // 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 a floating ip
+func (fi FloatingIPs) Delete(ctx context.Context, req DeleteRequest) (bool, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return false, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/floating_ip/delete"
+
+ res, err := fi.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
+}
diff --git a/pkg/sdn/flips/filter.go b/pkg/sdn/flips/filter.go
new file mode 100644
index 0000000..7ec25fe
--- /dev/null
+++ b/pkg/sdn/flips/filter.go
@@ -0,0 +1,42 @@
+package flips
+
+// FilterByID returns FloatingIPsList with specified ID.
+func (fil FloatingIPsList) FilterByID(id string) FloatingIPsList {
+ predicate := func(fi RecordFloatingIP) bool {
+ return fi.ID == id
+ }
+
+ return fil.FilterFunc(predicate)
+}
+
+// FilterByName returns FloatingIPsList with specified AccessGroupName.
+func (fil FloatingIPsList) FilterByAccessGroupName(name string) FloatingIPsList {
+ predicate := func(fi RecordFloatingIP) bool {
+ return fi.AccessGroupName == name
+ }
+
+ return fil.FilterFunc(predicate)
+}
+
+// FilterFunc allows filtering FloatingIPsList based on a user-specified predicate.
+func (fil FloatingIPsList) FilterFunc(predicate func(fi RecordFloatingIP) bool) FloatingIPsList {
+ var result FloatingIPsList
+
+ for _, acc := range fil.Objects {
+ if predicate(acc) {
+ result.Objects = append(result.Objects, acc)
+ }
+ }
+
+ return result
+}
+
+// FindOne returns first element.
+// If none was found, returns an empty struct.
+func (fil FloatingIPsList) FindOne() RecordFloatingIP {
+ if len(fil.Objects) == 0 {
+ return RecordFloatingIP{}
+ }
+
+ return fil.Objects[0]
+}
diff --git a/pkg/sdn/flips/filter_test.go b/pkg/sdn/flips/filter_test.go
new file mode 100644
index 0000000..49666ec
--- /dev/null
+++ b/pkg/sdn/flips/filter_test.go
@@ -0,0 +1,196 @@
+package flips
+
+import "testing"
+
+var testFloatingIPs = FloatingIPsList{
+ Objects: []RecordFloatingIP{
+ {
+ AccessGroupID: "testid",
+ AccessGroupName: "testname",
+ CreatedAt: "2025-09-23T08:05:59.271458Z",
+ ExternalNetworkPort: ExternalNetworkPort{
+ AccessGroupID: "somegroup",
+ AccessGroupName: "somename",
+ Comment: "some comment",
+ DisplayName: "some display name",
+ Enabled: true,
+ ExternalNetworkID: "someid",
+ ID: "someid",
+ IPv4: "someipv4",
+ MAC: "somemac",
+ VersionID: 1111111111111,
+ },
+ ID: "someid",
+ LogicalPort: LogicalPort{
+ ID: "someid",
+ AccessGroupID: "someid",
+ AccessGroupName: "somename",
+ AdapterMAC: "somemac",
+ AddressDetection: false,
+ Description: "some description",
+ DisplayName: "some display name",
+ Enabled: true,
+ Hypervisor: "hypervisor",
+ HypervisorDisplayName: "hypervisor display name",
+ UniqueIdentifier: "someid",
+ VersionID: 1111111111111,
+ CreatedAt: "2025-09-23T08:05:59.271458Z",
+ UpdatedAt: "2025-09-23T08:05:59.271458Z",
+ },
+ Router: Router{
+ ID: "someid",
+ AccessGroupID: "someid",
+ AccessGroupName: "somename",
+ CreatedAt: "2025-09-23T08:05:59.271458Z",
+ DisplayName: "some display name",
+ Enabled: true,
+ UpdatedAt: "2025-09-23T08:05:59.271458Z",
+ VersionID: 1111111111111,
+ },
+ UpdatedAt: "2025-09-23T08:05:59.271458Z",
+ VersionID: 1111111111111,
+ },
+ {
+ AccessGroupID: "testid3",
+ AccessGroupName: "testname3",
+ CreatedAt: "2025-09-23T08:05:59.271458Z",
+ ExternalNetworkPort: ExternalNetworkPort{
+ AccessGroupID: "somegroup",
+ AccessGroupName: "somename",
+ Comment: "some comment",
+ DisplayName: "some display name",
+ Enabled: true,
+ ExternalNetworkID: "someid2",
+ ID: "someid",
+ IPv4: "someipv4",
+ MAC: "somemac",
+ VersionID: 1111111111112,
+ },
+ ID: "someid2",
+ LogicalPort: LogicalPort{
+ ID: "someid2",
+ AccessGroupID: "someid",
+ AccessGroupName: "somename",
+ AdapterMAC: "somemac",
+ AddressDetection: false,
+ Description: "some description",
+ DisplayName: "some display name",
+ Enabled: true,
+ Hypervisor: "hypervisor",
+ HypervisorDisplayName: "hypervisor display name",
+ UniqueIdentifier: "someid",
+ VersionID: 1111111111112,
+ CreatedAt: "2025-09-23T08:05:59.271458Z",
+ UpdatedAt: "2025-09-23T08:05:59.271458Z",
+ },
+ Router: Router{
+ ID: "someid2",
+ AccessGroupID: "someid",
+ AccessGroupName: "somename",
+ CreatedAt: "2025-09-23T08:05:59.271458Z",
+ DisplayName: "some display name",
+ Enabled: true,
+ UpdatedAt: "2025-09-23T08:05:59.271458Z",
+ VersionID: 1111111111112,
+ },
+ UpdatedAt: "2025-09-23T08:05:59.271458Z",
+ VersionID: 1111111111112,
+ },
+ {
+ AccessGroupID: "testid3",
+ AccessGroupName: "testname3",
+ CreatedAt: "2025-09-23T08:05:59.271458Z",
+ ExternalNetworkPort: ExternalNetworkPort{
+ AccessGroupID: "somegroup",
+ AccessGroupName: "somename",
+ Comment: "some comment",
+ DisplayName: "some display name",
+ Enabled: true,
+ ExternalNetworkID: "someid3",
+ ID: "someid3",
+ IPv4: "someipv4",
+ MAC: "somemac",
+ VersionID: 1111111111113,
+ },
+ ID: "someid3",
+ LogicalPort: LogicalPort{
+ ID: "someid3",
+ AccessGroupID: "someid",
+ AccessGroupName: "somename",
+ AdapterMAC: "somemac",
+ AddressDetection: false,
+ Description: "some description",
+ DisplayName: "some display name",
+ Enabled: true,
+ Hypervisor: "hypervisor",
+ HypervisorDisplayName: "hypervisor display name",
+ UniqueIdentifier: "someid",
+ VersionID: 1111111111113,
+ CreatedAt: "2025-09-23T08:05:59.271458Z",
+ UpdatedAt: "2025-09-23T08:05:59.271458Z",
+ },
+ Router: Router{
+ ID: "someid3",
+ AccessGroupID: "someid",
+ AccessGroupName: "somename",
+ CreatedAt: "2025-09-23T08:05:59.271458Z",
+ DisplayName: "some display name",
+ Enabled: true,
+ UpdatedAt: "2025-09-23T08:05:59.271458Z",
+ VersionID: 1111111111113,
+ },
+ UpdatedAt: "2025-09-23T08:05:59.271458Z",
+ VersionID: 1111111111113,
+ },
+ },
+}
+
+func TestFilterByID(t *testing.T) {
+ actual := testFloatingIPs.FilterByID("someid").FindOne()
+
+ if actual.ID != "someid" {
+ t.Fatal("actual:", actual.ID, "> expected: someid")
+ }
+}
+
+func TestFilterByName(t *testing.T) {
+ actual := testFloatingIPs.FilterByAccessGroupName("testname").FindOne()
+
+ if actual.AccessGroupName != "testname" {
+ t.Fatal("actual:", actual.AccessGroupID, ">> expected: testname")
+ }
+}
+
+func TestFilterFunc(t *testing.T) {
+ actual := testFloatingIPs.FilterFunc(func(rfi RecordFloatingIP) bool {
+ return rfi.VersionID == 1111111111111
+ })
+
+ if len(actual.Objects) != 1 || actual.Objects[0].ID != "someid" {
+ t.Fatal("Expected 1 policy with version ID 1111111111111, found:", len(actual.Objects))
+ }
+}
+
+func TestFindOneWithResults(t *testing.T) {
+ result := testFloatingIPs.FilterByID("someid").FindOne()
+ if result.ID != "someid" {
+ t.Fatal("Expected someid, got:", result.ID)
+ }
+}
+
+func TestFindOneEmpty(t *testing.T) {
+ emptyList := FloatingIPsList{}
+ result := emptyList.FindOne()
+
+ if result.ID != "" || result.AccessGroupID != "" {
+ t.Fatal("Expected empty FloatingIP, got:", result)
+ }
+}
+
+func TestFilterByIDNotFound(t *testing.T) {
+ actual := testFloatingIPs.FilterByID("nonex")
+
+ if len(actual.Objects) != 0 {
+ t.Fatal("Expected 0 policies, found:", len(actual.Objects))
+ }
+}
diff --git a/pkg/sdn/flips/flips.go b/pkg/sdn/flips/flips.go
new file mode 100644
index 0000000..dcd6186
--- /dev/null
+++ b/pkg/sdn/flips/flips.go
@@ -0,0 +1,18 @@
+// API Actor API for managing SDN floating IPs
+package flips
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
+)
+
+// Structure for creating request to floating IPs
+type FloatingIPs struct {
+ client interfaces.Caller
+}
+
+// Builder for floating IPs endpoints
+func New(client interfaces.Caller) *FloatingIPs {
+ return &FloatingIPs{
+ client,
+ }
+}
diff --git a/pkg/sdn/flips/get.go b/pkg/sdn/flips/get.go
new file mode 100644
index 0000000..db135a0
--- /dev/null
+++ b/pkg/sdn/flips/get.go
@@ -0,0 +1,46 @@
+package flips
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+type GetRequest struct {
+ // ID of a floating IP
+ // Required: true
+ FloatingIPID string `url:"floating_ip_id" json:"floating_ip_id" validate:"required"`
+}
+
+// Get gets a floating ip details as a RecordFloatingIP struct
+func (fi FloatingIPs) Get(ctx context.Context, req GetRequest) (*RecordFloatingIP, error) {
+ res, err := fi.GetRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RecordFloatingIP{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+
+}
+
+// GetRaw gets a floating ip details as an array of bytes
+func (fi FloatingIPs) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/floating_ip/get"
+
+ res, err := fi.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/flips/list.go b/pkg/sdn/flips/list.go
new file mode 100644
index 0000000..8ac2ce3
--- /dev/null
+++ b/pkg/sdn/flips/list.go
@@ -0,0 +1,104 @@
+package flips
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// List of floating ips
+type ListRequest struct {
+ // Filter by access group ID
+ // Required: false
+ AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,omitempty"`
+
+ //Is the external network enabled
+ // Required: false
+ Enabled interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
+
+ // Filter by Pv4 of the associated external network port
+ // Required: false
+ ExternalNetworkPortIPv4 string `url:"external_network_port_ipv4,omitempty" json:"external_network_port_ipv4,omitempty"`
+
+ // Filter by IP of the associated logical port binding
+ // Required: false
+ LogicalPortBindingIP string `url:"logical_port_binding_ip,omitempty" json:"logical_port_binding_ip,omitempty"`
+
+ // Display name of the associated logical port
+ // Required: false
+ LogicalPortDisplayName string `url:"logical_port_display_name,omitempty" json:"logical_port_display_name,omitempty"`
+
+ // Filter by display name of the associated external network
+ // Required: false
+ ExternalNetworkDisplayName string `url:"external_network_display_name,omitempty" json:"external_network_display_name,omitempty"`
+
+ // Filter by display name of the associated router
+ // Required: false
+ RouterDisplayName string `url:"router_display_name,omitempty" json:"router_display_name,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 (enabled, created_at, updated_at, external_network_port_ipv4, logical_port_binding_ip, logical_port_display_name, external_network_display_name, router_display_name)
+ // 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 floating ips
+func (fi FloatingIPs) List(ctx context.Context, req ListRequest) (*FloatingIPsList, error) {
+ res, err := fi.ListRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ objects := []RecordFloatingIP{}
+
+ err = json.Unmarshal(res, &objects)
+ if err != nil {
+ return nil, err
+ }
+
+ result := FloatingIPsList{Objects: objects}
+
+ return &result, nil
+}
+
+// ListRaw gets a list of all floating ips as an array of bytes
+func (fi FloatingIPs) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
+
+ if err := validators.ValidateRequest(req); err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/floating_ip/list"
+
+ res, err := fi.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/flips/models.go b/pkg/sdn/flips/models.go
new file mode 100644
index 0000000..a08632a
--- /dev/null
+++ b/pkg/sdn/flips/models.go
@@ -0,0 +1,145 @@
+package flips
+
+// List of floating ips
+type FloatingIPsList struct {
+ Objects []RecordFloatingIP
+}
+
+// Main info about a floating ip
+type RecordFloatingIP struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Details of an external network port
+ ExternalNetworkPort ExternalNetworkPort `json:"external_network_port"`
+
+ // ID of a floating IP
+ ID string `json:"id"`
+
+ // Details of a logical port
+ LogicalPort LogicalPort `json:"logical_port"`
+
+ // Details of a router
+ Router Router `json:"router"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Info about a router
+type Router struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Is a security policy enabled
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Info about a logical port
+type LogicalPort struct {
+
+ // ID
+ ID string `json:"id"`
+
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Adapter MAC
+ AdapterMAC string `json:"adapter_mac"`
+
+ // Is address detected
+ AddressDetection bool `json:"address_detection"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Is a logical port enabled
+ Enabled bool `json:"enabled"`
+
+ // Hypervisor
+ Hypervisor string `json:"hypervisor"`
+
+ // Hypervisor display name
+ HypervisorDisplayName string `json:"hypervisor_display_name"`
+
+ // Unique identifier
+ UniqueIdentifier string `json:"unique_identifier"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Details of external network ports
+type ExternalNetworkPort struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Comment
+ Comment string `json:"comment"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Is a security policy enabled
+ Enabled bool `json:"enabled"`
+
+ // External network ID
+ ExternalNetworkID string `json:"external_network_id"`
+
+ // ID
+ ID string `json:"id"`
+
+ // IP v4
+ IPv4 string `json:"ipv4"`
+
+ // MAC
+ MAC string `json:"mac"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
diff --git a/pkg/sdn/flips/update.go b/pkg/sdn/flips/update.go
new file mode 100644
index 0000000..c5171f8
--- /dev/null
+++ b/pkg/sdn/flips/update.go
@@ -0,0 +1,57 @@
+package flips
+
+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 a floating ip
+type UpdateRequest struct {
+ // ID of a floating IP
+ // Required: true
+ FloatingIPID string `url:"floating_ip_id" json:"floating_ip_id" validate:"required"`
+
+ // Version ID
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // ID of an external network port
+ // Required: true
+ ExtNetPortID string `url:"external_network_port_id" json:"external_network_port_id" validate:"required"`
+
+ // ID of a logical network port
+ // Required: true
+ LogicalPortID string `url:"logical_port_id" json:"logical_port_id" validate:"required"`
+
+ // ID of a router
+ // Required: true
+ RouterID string `url:"router_id" json:"router_id" validate:"required"`
+}
+
+// Update updates a floating ip
+func (fi FloatingIPs) Update(ctx context.Context, req UpdateRequest) (*RecordFloatingIP, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/floating_ip/update"
+
+ res, err := fi.client.DecortApiCallCtype(ctx, http.MethodPut, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RecordFloatingIP{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/logical_ports.go b/pkg/sdn/logical_ports.go
new file mode 100644
index 0000000..b69ee19
--- /dev/null
+++ b/pkg/sdn/logical_ports.go
@@ -0,0 +1,10 @@
+package sdn
+
+import (
+ lp "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/logicalports"
+)
+
+// Accessing the SDN method group
+func (sdn *SDN) LogicalPorts() *lp.LogicalPorts {
+ return lp.New(sdn.client)
+}
diff --git a/pkg/sdn/logicalports/create.go b/pkg/sdn/logicalports/create.go
new file mode 100644
index 0000000..66cd731
--- /dev/null
+++ b/pkg/sdn/logicalports/create.go
@@ -0,0 +1,112 @@
+package logicalports
+
+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 logical port
+type CreateRequest struct {
+ // ID of the logical port
+ // Required: true
+ LogicalPortID string `url:"logical_port_id" json:"logical_port_id" validate:"required"`
+
+ // ID of the version
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // ID of the access group
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // Description
+ // Required: true
+ Description string `url:"description" json:"description" validate:"required"`
+
+ // Display name
+ // Required: true
+ DisplayName string `url:"display_name" json:"display_name" validate:"required"`
+
+ // Enabled. True or False
+ // Required: true
+ Enabled interface{} `url:"enabled" json:"enabled" validate:"required,isBool"`
+
+ // Is excluded from firewall. True or False
+ // Required: true
+ IsExcludedFromFirewall interface{} `url:"is_excluded_from_firewall" json:"is_excluded_from_firewall" validate:"required,isBool"`
+
+ // Hypervisor
+ // Required: true
+ Hypervisor string `url:"hypervisor" json:"hypervisor" validate:"required"`
+
+ // Port security. True or False
+ // Required: true
+ PortSecurity interface{} `url:"port_security" json:"port_security" validate:"required,isBool"`
+
+ // Segment ID
+ // Required: true
+ SegmentID string `url:"segment_id" json:"segment_id" validate:"required"`
+
+ // Adapter MAC
+ // Required: false
+ AdapterMAC string `url:"adapter_mac,omitempty" json:"adapter_mac,omitempty"`
+
+ // Unique identifier
+ // Required: false
+ UniqueIdentifier string `url:"unique_identifier,omitempty" json:"unique_identifier,omitempty"`
+
+ // Logical port addresses
+ // Required: false
+ LogicalPortAddresses []LogicalPortAddress `url:"logical_port_addresses,omitempty" json:"logical_port_addresses,omitempty" validate:"dive"`
+}
+
+// LogicalPortAddressRequest struct representing logical port address
+type LogicalPortAddressRequest struct {
+ // IP address
+ // Required: true
+ IP string `url:"ip" json:"ip" validate:"required"`
+
+ // IP type
+ // Required: true
+ IPType string `url:"ip_type" json:"ip_type" validate:"required,oneof=IPv4 IPv6"`
+
+ // Is primary. True or False
+ // Required: true
+ IsPrimary interface{} `url:"is_primary" json:"is_primary" validate:"required,isBool"`
+
+ // MAC address
+ // Required: false
+ MAC string `url:"mac,omitempty" json:"mac,omitempty"`
+
+ // Is discovered. True or False
+ // Required: false
+ IsDiscovered interface{} `url:"is_discovered,omitempty" json:"is_discovered,omitempty" validate:"omitempty,isBool"`
+}
+
+// Create creates a logical port
+func (l LogicalPorts) Create(ctx context.Context, req CreateRequest) (*LogicalPort, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/logical_port/create"
+
+ res, err := l.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := LogicalPort{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/logicalports/delete.go b/pkg/sdn/logicalports/delete.go
new file mode 100644
index 0000000..f1d3ce3
--- /dev/null
+++ b/pkg/sdn/logicalports/delete.go
@@ -0,0 +1,41 @@
+package logicalports
+
+import (
+ "context"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// DeleteRequest struct to delete logical port
+type DeleteRequest struct {
+ // Port ID
+ // Required: true
+ ID string `url:"logical_port_id" json:"logical_port_id" validate:"required"`
+
+ // Version
+ // Required: true
+ Version uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // Force delete. True or false
+ // Required: false
+ Force interface{} `url:"force,omitempty" json:"force,omitempty" validate:"omitempty,isBool"`
+}
+
+// Delete a logical port
+func (i LogicalPorts) Delete(ctx context.Context, req DeleteRequest) (bool, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return false, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/logical_port/delete"
+
+ _, err = i.client.DecortApiCallCtype(ctx, http.MethodDelete, url, constants.MIMEJSON, req)
+ if err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
diff --git a/pkg/sdn/logicalports/filter.go b/pkg/sdn/logicalports/filter.go
new file mode 100644
index 0000000..62b57ac
--- /dev/null
+++ b/pkg/sdn/logicalports/filter.go
@@ -0,0 +1,42 @@
+package logicalports
+
+// FilterByID returns LogicalPortsList with specified ID.
+func (agl LogicalPortsList) FilterByID(id string) LogicalPortsList {
+ predicate := func(ia LogicalPort) bool {
+ return ia.ID == id
+ }
+
+ return agl.FilterFunc(predicate)
+}
+
+// FilterByName returns LogicalPortsList with specified Name.
+func (agl LogicalPortsList) FilterByName(name string) LogicalPortsList {
+ predicate := func(ia LogicalPort) bool {
+ return ia.DisplayName == name
+ }
+
+ return agl.FilterFunc(predicate)
+}
+
+// FilterFunc allows filtering LogicalPortsList based on a user-specified predicate.
+func (agl LogicalPortsList) FilterFunc(predicate func(LogicalPort) bool) LogicalPortsList {
+ var result LogicalPortsList
+
+ for _, acc := range agl.Ports {
+ if predicate(acc) {
+ result.Ports = append(result.Ports, acc)
+ }
+ }
+
+ return result
+}
+
+// FindOne returns first element.
+// If none was found, returns an empty struct.
+func (agl LogicalPortsList) FindOne() LogicalPort {
+ if len(agl.Ports) == 0 {
+ return LogicalPort{}
+ }
+
+ return agl.Ports[0]
+}
diff --git a/pkg/sdn/logicalports/get.go b/pkg/sdn/logicalports/get.go
new file mode 100644
index 0000000..652766a
--- /dev/null
+++ b/pkg/sdn/logicalports/get.go
@@ -0,0 +1,47 @@
+package logicalports
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// GetRequest struct to get information about logical port
+type GetRequest struct {
+ // ID a logical port
+ // Required: true
+ ID string `url:"logical_port_id" json:"logical_port_id" validate:"required"`
+}
+
+// Get gets logical port details as a LogicalPort struct
+func (a LogicalPorts) Get(ctx context.Context, req GetRequest) (*LogicalPort, error) {
+ res, err := a.GetRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := LogicalPort{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+
+}
+
+// GetRaw gets logical port details as an array of bytes
+func (a LogicalPorts) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/logical_port/get"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/logicalports/get_by_unique_identifier.go b/pkg/sdn/logicalports/get_by_unique_identifier.go
new file mode 100644
index 0000000..c4ee81f
--- /dev/null
+++ b/pkg/sdn/logicalports/get_by_unique_identifier.go
@@ -0,0 +1,47 @@
+package logicalports
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// GetByUniqueIdentifierRequest struct to get information about logical port
+type GetByUniqueIdentifierRequest struct {
+ // ID a logical port
+ // Required: true
+ ID string `url:"unique_identifier" json:"unique_identifier" validate:"required"`
+}
+
+// GetByUniqueIdentifier gets logical port details as a LogicalPort struct
+func (a LogicalPorts) GetByUniqueIdentifier(ctx context.Context, req GetByUniqueIdentifierRequest) (*LogicalPort, error) {
+ res, err := a.GetByUniqueIdentifierRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := LogicalPort{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+
+}
+
+// GetByUniqueIdentifier gets logical port details as an array of bytes
+func (a LogicalPorts) GetByUniqueIdentifierRaw(ctx context.Context, req GetByUniqueIdentifierRequest) ([]byte, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/logical_port/get_by_unique_identifier"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/logicalports/ids.go b/pkg/sdn/logicalports/ids.go
new file mode 100644
index 0000000..91e3175
--- /dev/null
+++ b/pkg/sdn/logicalports/ids.go
@@ -0,0 +1,10 @@
+package logicalports
+
+// IDs gets array of IDs from LogicalPortList struct
+func (pl LogicalPortsList) IDs() []string {
+ res := make([]string, 0, len(pl.Ports))
+ for _, c := range pl.Ports {
+ res = append(res, c.ID)
+ }
+ return res
+}
diff --git a/pkg/sdn/logicalports/list.go b/pkg/sdn/logicalports/list.go
new file mode 100644
index 0000000..a980e44
--- /dev/null
+++ b/pkg/sdn/logicalports/list.go
@@ -0,0 +1,120 @@
+package logicalports
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// ListRequest struct to get a list of logical ports
+type ListRequest struct {
+ // Find by access group ID
+ // Required: false
+ AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,omitempty"`
+
+ // Find by segment ID
+ // Required: false
+ SegmentID string `url:"segment_id,omitempty" json:"segment_id,omitempty"`
+
+ // Find by segment display name
+ // Required: false
+ SegmentDisplayName string `url:"segment_display_name,omitempty" json:"segment_display_name,omitempty"`
+
+ // Find by external network ID
+ // Required: false
+ ExternalNetworkID string `url:"external_network_id,omitempty" json:"external_network_id,omitempty"`
+
+ // Find by unique identifier
+ // Required: false
+ UniqueIdentifier string `url:"unique_identifier,omitempty" json:"unique_identifier,omitempty"`
+
+ // Find by display name
+ // Required: false
+ DisplayName string `url:"display_name,omitempty" json:"display_name,omitempty"`
+
+ // Find by adapter MAC address
+ // Required: false
+ AdapterMAC string `url:"adapter_mac,omitempty" json:"adapter_mac,omitempty"`
+
+ // Find by hypervisor
+ // Required: false
+ Hypervisor string `url:"hypervisor,omitempty" json:"hypervisor,omitempty"`
+
+ // Find by hypervisor display name
+ // Required: false
+ HypervisorDisplayName string `url:"hypervisor_display_name,omitempty" json:"hypervisor_display_name,omitempty"`
+
+ // Find by live migration target hypervisor
+ // Required: false
+ LiveMigrationTargetHv string `url:"live_migration_target_hv,omitempty" json:"live_migration_target_hv,omitempty"`
+
+ // Find by port security status, true or false
+ // Required: false
+ PortSecurity interface{} `url:"port_security,omitempty" json:"port_security,omitempty" validate:"omitempty,isBool"`
+
+ // Find by address detection status, true or false
+ // Required: false
+ AddressDetection interface{} `url:"address_detection,omitempty" json:"address_detection,omitempty" validate:"omitempty,isBool"`
+
+ // Find by enabled status, true or false
+ // Required: false
+ Enabled interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
+
+ // Creation date lower bound (inclusive)
+ // Required: false
+ CreatedFrom string `url:"created_from,omitempty" json:"created_from,omitempty"`
+
+ // Creation date upper bound (inclusive)
+ // 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, segment_id, hypervisor, port_security, segment_display_name, primary_address, hypervisor_display_name)
+ // 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 logical portts
+func (i LogicalPorts) List(ctx context.Context, req ListRequest) (*LogicalPortsList, error) {
+ res, err := i.ListRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ groups := []LogicalPort{}
+
+ err = json.Unmarshal(res, &groups)
+ if err != nil {
+ return nil, err
+ }
+
+ result := LogicalPortsList{Ports: groups}
+
+ return &result, nil
+}
+
+// ListRaw gets a list of all logical portts as an array of bytes
+func (a LogicalPorts) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
+
+ if err := validators.ValidateRequest(req); err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/logical_port/list"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/logicalports/logicalports.go b/pkg/sdn/logicalports/logicalports.go
new file mode 100644
index 0000000..b164a7d
--- /dev/null
+++ b/pkg/sdn/logicalports/logicalports.go
@@ -0,0 +1,18 @@
+// API Actor API for managing SDN logical ports
+package logicalports
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
+)
+
+// Structure for creating request to logical ports
+type LogicalPorts struct {
+ client interfaces.Caller
+}
+
+// Builder for logical ports endpoints
+func New(client interfaces.Caller) *LogicalPorts {
+ return &LogicalPorts{
+ client,
+ }
+}
diff --git a/pkg/sdn/logicalports/migrate_cancel.go b/pkg/sdn/logicalports/migrate_cancel.go
new file mode 100644
index 0000000..9a9859a
--- /dev/null
+++ b/pkg/sdn/logicalports/migrate_cancel.go
@@ -0,0 +1,44 @@
+package logicalports
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// MigrateCancelRequest struct to cancel migrate
+type MigrateCancelRequest struct {
+ // ID of the logical port
+ // Required: true
+ LogicalPortID string `url:"logical_port_id" json:"logical_port_id" validate:"required"`
+
+ // ID of the version
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+}
+
+func (i LogicalPorts) CancelMigrate(ctx context.Context, req MigrateCancelRequest) (*MigrationStatus, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/logical_port/migration_cancel"
+
+ res, err := i.client.DecortApiCallCtype(ctx, http.MethodDelete, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := MigrationStatus{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/logicalports/migrate_start.go b/pkg/sdn/logicalports/migrate_start.go
new file mode 100644
index 0000000..1c238c9
--- /dev/null
+++ b/pkg/sdn/logicalports/migrate_start.go
@@ -0,0 +1,48 @@
+package logicalports
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// MigrateStartRequest struct to start migrate
+type MigrateStartRequest struct {
+ // ID of the logical port
+ // Required: true
+ LogicalPortID string `url:"logical_port_id" json:"logical_port_id" validate:"required"`
+
+ // ID of the version
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // Hypervisor
+ // Required: true
+ TargetHypervisor string `url:"target_hypervisor" json:"target_hypervisor" validate:"required"`
+}
+
+func (i LogicalPorts) StartMigrate(ctx context.Context, req MigrateStartRequest) (*MigrationStatus, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/logical_port/migration_start"
+
+ res, err := i.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := MigrationStatus{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/logicalports/models.go b/pkg/sdn/logicalports/models.go
new file mode 100644
index 0000000..c3c009f
--- /dev/null
+++ b/pkg/sdn/logicalports/models.go
@@ -0,0 +1,158 @@
+package logicalports
+
+// LogicalPortsList represents a list of logical ports
+type LogicalPortsList struct {
+ Ports []LogicalPort `json:"ports"`
+}
+
+// Main information about logical port
+type LogicalPort struct {
+ // ID
+ ID string `json:"id"`
+
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Adapter MAC
+ AdapterMAC string `json:"adapter_mac"`
+
+ // Address detection
+ AddressDetection bool `json:"address_detection"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled
+ Enabled bool `json:"enabled"`
+
+ // External network ID
+ ExternalNetworkID string `json:"external_network_id"`
+
+ // Hypervisor
+ Hypervisor string `json:"hypervisor"`
+
+ // Hypervisor display name
+ HypervisorDisplayName string `json:"hypervisor_display_name"`
+
+ // Live migration target hypervisor
+ LiveMigrationTargetHV string `json:"live_migration_target_hv"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // Bindings information
+ Bindings Bindings `json:"bindings"`
+
+ // Unique identifier
+ UniqueIdentifier string `json:"unique_identifier"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Status information
+type Status struct {
+ // Common status
+ Common string `json:"common"`
+
+ // Hypervisors status
+ Hypervisors []HypervisorStatus `json:"hypervisors"`
+}
+
+// HypervisorStatus 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"`
+
+ // Synced time
+ SyncedAt string `json:"synced_at"`
+}
+
+// Bindings information
+type Bindings struct {
+ // ID
+ ID string `json:"id"`
+
+ // Segment display name
+ 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 excluded from firewall
+ IsExcludedFromFirewall bool `json:"is_excluded_from_firewall"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Logical port addresses
+ LogicalPortAddresses []LogicalPortAddress `json:"logical_port_addresses"`
+}
+
+// LogicalPortAddress information
+type LogicalPortAddress struct {
+ // IP address
+ IP string `json:"ip"`
+
+ // IP type
+ IPType string `json:"ip_type"`
+
+ // Is discovered
+ IsDiscovered bool `json:"is_discovered"`
+
+ // Is primary
+ IsPrimary bool `json:"is_primary"`
+
+ // MAC address
+ MAC string `json:"mac"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Logical port ID
+ LogicalPortID string `json:"logical_port_id"`
+
+ // Assigned time
+ AssignedAt string `json:"assigned_at"`
+}
+
+type MigrationStatus struct {
+ // ID
+ ID string `json:"id"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
diff --git a/pkg/sdn/logicalports/serialize.go b/pkg/sdn/logicalports/serialize.go
new file mode 100644
index 0000000..e9a0d2e
--- /dev/null
+++ b/pkg/sdn/logicalports/serialize.go
@@ -0,0 +1,43 @@
+package logicalports
+
+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 LogicalPortsList) Serialize(params ...string) (serialization.Serialized, error) {
+ if len(la.Ports) == 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 LogicalPort) 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)
+}
diff --git a/pkg/sdn/logicalports/update.go b/pkg/sdn/logicalports/update.go
new file mode 100644
index 0000000..1fb46b6
--- /dev/null
+++ b/pkg/sdn/logicalports/update.go
@@ -0,0 +1,135 @@
+package logicalports
+
+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 logical port
+type UpdateRequest struct {
+ // ID of the logical port
+ // Required: true
+ LogicalPortID string `url:"logical_port_id" json:"logical_port_id" validate:"required"`
+
+ // ID of the version
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // Adapter MAC
+ // Required: true
+ AdapterMAC string `url:"adapter_mac" json:"adapter_mac" validate:"required"`
+
+ // Description
+ // Required: true
+ Description string `url:"description" json:"description" validate:"required"`
+
+ // Display name
+ // Required: true
+ DisplayName string `url:"display_name" json:"display_name" validate:"required"`
+
+ // Enabled. True or False
+ // Required: true
+ Enabled interface{} `url:"enabled" json:"enabled" validate:"required,isBool"`
+
+ // Remove addresses
+ // Required: false
+ RemoveAddresses []string `url:"remove_addresses,omitempty" json:"remove_addresses,omitempty"`
+
+ // Hypervisor
+ // Required: true
+ Hypervisor string `url:"hypervisor" json:"hypervisor" validate:"required"`
+
+ // Port security. True or False
+ // Required: true
+ PortSecurity interface{} `url:"port_security" json:"port_security" validate:"required,isBool"`
+
+ // Is excluded from firewall. True or False
+ // Required: true
+ IsExcludedFromFirewall interface{} `url:"is_excluded_from_firewall" json:"is_excluded_from_firewall" validate:"required,isBool"`
+
+ // Segment ID
+ // Required: true
+ SegmentID string `url:"segment_id" json:"segment_id" validate:"required"`
+
+ // Update addresses
+ // Required: false
+ UpdateAddresses []UpdateAddress `url:"update_addresses,omitempty" json:"update_addresses,omitempty" validate:"dive"`
+
+ // Add addresses
+ // Required: false
+ AddAddresses []AddAddress `url:"add_addresses,omitempty" json:"add_addresses,omitempty" validate:"dive"`
+}
+
+// UpdateAddress struct representing update address
+type UpdateAddress struct {
+ // IP address
+ // Required: true
+ IP string `url:"ip" json:"ip" validate:"required"`
+
+ // IP type
+ // Required: true
+ IPType string `url:"ip_type" json:"ip_type" validate:"required,oneof=IPv4 IPv6"`
+
+ // Is discovered. True or False
+ // Required: false
+ IsDiscovered interface{} `url:"is_discovered,omitempty" json:"is_discovered,omitempty" validate:"omitempty,isBool"`
+
+ // Is primary. True or False
+ // Required: true
+ IsPrimary interface{} `url:"is_primary" json:"is_primary" validate:"required,isBool"`
+
+ // MAC address
+ // Required: false
+ MAC string `url:"mac,omitempty" json:"mac,omitempty"`
+}
+
+// AddAddress struct representing add address
+type AddAddress struct {
+ // IP address
+ // Required: true
+ IP string `url:"ip" json:"ip" validate:"required"`
+
+ // IP type
+ // Required: true
+ IPType string `url:"ip_type" json:"ip_type" validate:"required,oneof=IPv4 IPv6"`
+
+ // Is discovered. True or False
+ // Required: false
+ IsDiscovered interface{} `url:"is_discovered,omitempty" json:"is_discovered,omitempty" validate:"omitempty,isBool"`
+
+ // Is primary. True or False
+ // Required: true
+ IsPrimary interface{} `url:"is_primary" json:"is_primary" validate:"required,isBool"`
+
+ // MAC address
+ // Required: false
+ MAC string `url:"mac,omitempty" json:"mac,omitempty"`
+}
+
+// Update updates a logical port
+func (i LogicalPorts) Update(ctx context.Context, req UpdateRequest) (*LogicalPort, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/logical_port/update"
+
+ res, err := i.client.DecortApiCallCtype(ctx, http.MethodPut, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := LogicalPort{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/netobjgroups/attach_extnet_ports.go b/pkg/sdn/netobjgroups/attach_extnet_ports.go
new file mode 100644
index 0000000..329637d
--- /dev/null
+++ b/pkg/sdn/netobjgroups/attach_extnet_ports.go
@@ -0,0 +1,41 @@
+package netobjgroups
+
+import (
+ "context"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// AttachExtNetPortsRequest struct to attach external network ports to a network object group
+type AttachExtNetPortsRequest struct {
+ // ID of a network object group
+ // Required: true
+ ObjectGroupID string `url:"object_group_id" json:"object_group_id" validate:"required"`
+
+ // ID of an access group
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // IDs of external network ports to attach to a network object group
+ // Required: true
+ PortIDs []string `url:"port_ids" json:"port_ids" validate:"required"`
+}
+
+// AttachExtNetPorts attaches external network ports to a network object group
+func (nog NetworkObjectGroups) AttachExtNetPorts(ctx context.Context, req AttachExtNetPortsRequest) (bool, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return false, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/network_object_group/attach_external_network_ports"
+
+ _, err = nog.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
diff --git a/pkg/sdn/netobjgroups/attach_logical_ports.go b/pkg/sdn/netobjgroups/attach_logical_ports.go
new file mode 100644
index 0000000..7048a08
--- /dev/null
+++ b/pkg/sdn/netobjgroups/attach_logical_ports.go
@@ -0,0 +1,54 @@
+package netobjgroups
+
+import (
+ "context"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// AttachLogicalPortsRequest struct to attach logical ports to a network object group
+type AttachLogicalPortsRequest struct {
+ // ID of a network object group
+ // Required: true
+ ObjectGroupID string `url:"object_group_id" json:"object_group_id" validate:"required"`
+
+ // ID of an access group
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // Version ID
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // Port Bindings
+ // Required: true
+ PortBindings []PortBindings `url:"port_bindings" json:"port_bindings" validate:"required,dive"`
+}
+type PortBindings struct {
+ // ID of a logical port
+ // Required: true
+ PortID string `url:"port_id" json:"port_id" validate:"required"`
+
+ // Version of a logical port
+ // Required: true
+ PortVersion int64 `url:"port_version" json:"port_version" validate:"required"`
+}
+
+// AttachLogicalPorts attaches logical ports to a network object group
+func (nog NetworkObjectGroups) AttachLogicalPorts(ctx context.Context, req AttachLogicalPortsRequest) (bool, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return false, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/network_object_group/attach_logical_ports"
+
+ _, err = nog.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
diff --git a/pkg/sdn/netobjgroups/create.go b/pkg/sdn/netobjgroups/create.go
new file mode 100644
index 0000000..b1d8862
--- /dev/null
+++ b/pkg/sdn/netobjgroups/create.go
@@ -0,0 +1,62 @@
+package netobjgroups
+
+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 a network object group
+type CreateRequest struct {
+ // Access Group ID
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // Description
+ // Required: true
+ Description string `url:"description" json:"description" validate:"required"`
+
+ // Name
+ // Required: true
+ Name string `url:"name" json:"name" validate:"required"`
+
+ // Logical ports bindings
+ // Required: false
+ LogicalPortsBindings []LogicalPortsBindings `url:"logical_ports_binding,omitempty" json:"logical_ports_bindings,omitempty" validate:"omitempty,dive"`
+}
+type LogicalPortsBindings struct {
+ // Port ID
+ // Required: true
+ PortID string `url:"port_id" json:"port_id" validate:"required"`
+
+ // Port version
+ // Required: true
+ PortVersion int64 `url:"port_version" json:"port_version" validate:"required"`
+}
+
+// Create creates a network object group
+func (nog NetworkObjectGroups) Create(ctx context.Context, req CreateRequest) (*RecordNetObjGroup, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/network_object_group/create"
+
+ res, err := nog.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RecordNetObjGroup{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/netobjgroups/delete.go b/pkg/sdn/netobjgroups/delete.go
new file mode 100644
index 0000000..02cdebe
--- /dev/null
+++ b/pkg/sdn/netobjgroups/delete.go
@@ -0,0 +1,51 @@
+package netobjgroups
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// DeleteRequest to delete a network object group
+type DeleteRequest struct {
+ // ID of a network object group
+ // Required: true
+ ObjectGroupID string `url:"object_group_id" json:"object_group_id" validate:"required"`
+
+ // Version ID
+ // 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 a network object group
+func (nog NetworkObjectGroups) Delete(ctx context.Context, req DeleteRequest) (bool, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return false, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/network_object_group/delete"
+
+ res, err := nog.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
+}
diff --git a/pkg/sdn/netobjgroups/detach_external_network_ports.go b/pkg/sdn/netobjgroups/detach_external_network_ports.go
new file mode 100644
index 0000000..72f194e
--- /dev/null
+++ b/pkg/sdn/netobjgroups/detach_external_network_ports.go
@@ -0,0 +1,49 @@
+package netobjgroups
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// DetachExtNetPortsRequest struct to detach an external network port from a network object group
+type DetachExtNetPortsRequest struct {
+ // ID of a network object group
+ // Required: true
+ ObjectGroupID string `url:"object_group_id" json:"object_group_id" validate:"required"`
+
+ // ID of an access group
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // ID of an external network port to detach from a network object group
+ // Required: true
+ ExternalNetworkPortID string `url:"external_network_port_id" json:"external_network_port_id" validate:"required"`
+}
+
+// DetachExtNetlPorts detaches external network ports from a network object group
+func (nog NetworkObjectGroups) DetachExtNetPorts(ctx context.Context, req DetachExtNetPortsRequest) (*RecordVersion, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/network_object_group/detach_external_network_ports"
+
+ res, err := nog.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RecordVersion{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/netobjgroups/detach_logical_ports.go b/pkg/sdn/netobjgroups/detach_logical_ports.go
new file mode 100644
index 0000000..782fda0
--- /dev/null
+++ b/pkg/sdn/netobjgroups/detach_logical_ports.go
@@ -0,0 +1,57 @@
+package netobjgroups
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// DetachLogicalPortsRequest struct to detach an logical port from a network object group
+type DetachLogicalPortsRequest struct {
+ // ID of a network object group
+ // Required: true
+ ObjectGroupID string `url:"object_group_id" json:"object_group_id" validate:"required"`
+
+ // ID of an access group
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // Version ID
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // ID of a logical port
+ // Required: true
+ LogicalPortID string `url:"logical_port_id" json:"logical_port_id" validate:"required"`
+
+ // Version of a logical port
+ // Required: true
+ LogicalPortVersion uint64 `url:"logical_port_version" json:"logical_port_version" validate:"required"`
+}
+
+// DetachLogicalPorts detaches logical ports from a network object group
+func (nog NetworkObjectGroups) DetachLogicalPorts(ctx context.Context, req DetachLogicalPortsRequest) (*RecordVersion, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/network_object_group/detach_logical_ports"
+
+ res, err := nog.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RecordVersion{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/netobjgroups/filter.go b/pkg/sdn/netobjgroups/filter.go
new file mode 100644
index 0000000..706fa5f
--- /dev/null
+++ b/pkg/sdn/netobjgroups/filter.go
@@ -0,0 +1,42 @@
+package netobjgroups
+
+// FilterByID returns NetObjGroupList with specified ID.
+func (nog NetObjGroupList) FilterByID(id string) NetObjGroupList {
+ predicate := func(netobj RecordNetObjGroup) bool {
+ return netobj.ID == id
+ }
+
+ return nog.FilterFunc(predicate)
+}
+
+// FilterByName returns NetObjGroupList with specified Name.
+func (nog NetObjGroupList) FilterByName(name string) NetObjGroupList {
+ predicate := func(netobj RecordNetObjGroup) bool {
+ return netobj.Name == name
+ }
+
+ return nog.FilterFunc(predicate)
+}
+
+// FilterFunc allows filtering NetObjGroupList based on a user-specified predicate.
+func (nog NetObjGroupList) FilterFunc(predicate func(group RecordNetObjGroup) bool) NetObjGroupList {
+ var result NetObjGroupList
+
+ for _, acc := range nog.Objects {
+ if predicate(acc) {
+ result.Objects = append(result.Objects, acc)
+ }
+ }
+
+ return result
+}
+
+// FindOne returns first element.
+// If none was found, returns an empty struct.
+func (nog NetObjGroupList) FindOne() RecordNetObjGroup {
+ if len(nog.Objects) == 0 {
+ return RecordNetObjGroup{}
+ }
+
+ return nog.Objects[0]
+}
diff --git a/pkg/sdn/netobjgroups/filter_test.go b/pkg/sdn/netobjgroups/filter_test.go
new file mode 100644
index 0000000..c12166c
--- /dev/null
+++ b/pkg/sdn/netobjgroups/filter_test.go
@@ -0,0 +1,111 @@
+package netobjgroups
+
+import "testing"
+
+var testNetObjGroups = NetObjGroupList{
+ Objects: []RecordNetObjGroup{
+ {
+ AccessGroupID: "d85d4a08-3216-4240-a8bd-191cd9cc3bf3",
+ AccessGroupName: "Test1",
+ CreatedAt: "2025-09-04T13:18:14.412118Z",
+ Description: "test descr",
+ ID: "0b16493b-0a38-4c90-80a0-c19f898f593d",
+ Name: "Test1",
+ UpdatedAt: "2025-10-28T07:28:15.450717Z",
+ VersionID: 1761636495441,
+ Counters: Counter{
+ LogicalPortsCount: 1,
+ SecurityPoliciesCount: 2,
+ SecurityRulesCount: 0,
+ },
+ },
+ {
+ AccessGroupID: "d85d4a08-3216-4240-a8bd-191cd9cc3bf3",
+ AccessGroupName: "Test2",
+ CreatedAt: "2025-09-04T13:18:14.412118Z",
+ Description: "another descr",
+ ID: "0b16493b-0a38-4c90-80a0-c19f898f593e",
+ Name: "Test2",
+ UpdatedAt: "2025-10-28T07:28:15.450717Z",
+ VersionID: 1761636495442,
+ Counters: Counter{
+ LogicalPortsCount: 1,
+ SecurityPoliciesCount: 2,
+ SecurityRulesCount: 0,
+ },
+ },
+ {
+ AccessGroupID: "d85d4a08-3216-4240-a8bd-191cd9cc3bf3",
+ AccessGroupName: "Test3",
+ CreatedAt: "2025-09-04T13:18:14.412118Z",
+ Description: "another descr",
+ ID: "0b16493b-0a38-4c90-80a0-c19f898f593f",
+ Name: "Test3",
+ UpdatedAt: "2025-10-28T07:28:15.450717Z",
+ VersionID: 1761636495443,
+ Counters: Counter{
+ LogicalPortsCount: 1,
+ SecurityPoliciesCount: 2,
+ SecurityRulesCount: 0,
+ },
+ },
+ },
+}
+
+func TestFilterByID(t *testing.T) {
+ actual := testNetObjGroups.FilterByID("0b16493b-0a38-4c90-80a0-c19f898f593d").FindOne()
+
+ if actual.ID != "0b16493b-0a38-4c90-80a0-c19f898f593d" {
+ t.Fatal("actual:", actual.ID, "> expected: 0b16493b-0a38-4c90-80a0-c19f898f593d")
+ }
+}
+
+func TestFilterByName(t *testing.T) {
+ actual := testNetObjGroups.FilterByName("Test1").FindOne()
+
+ if actual.Name != "Test1" {
+ t.Fatal("actual:", actual.Name, ">> expected: Test1")
+ }
+}
+
+func TestFilterFunc(t *testing.T) {
+ actual := testNetObjGroups.FilterFunc(func(rnog RecordNetObjGroup) bool {
+ return rnog.Description == "test descr"
+ })
+
+ if len(actual.Objects) != 1 || actual.Objects[0].ID != "0b16493b-0a38-4c90-80a0-c19f898f593d" {
+ t.Fatal("Expected 1 policy with description 'Second policy', found:", len(actual.Objects))
+ }
+}
+
+func TestFindOneWithResults(t *testing.T) {
+ result := testNetObjGroups.FilterByID("0b16493b-0a38-4c90-80a0-c19f898f593d").FindOne()
+ if result.ID != "0b16493b-0a38-4c90-80a0-c19f898f593d" {
+ t.Fatal("Expected 0b16493b-0a38-4c90-80a0-c19f898f593d, got:", result.ID)
+ }
+}
+
+func TestFindOneEmpty(t *testing.T) {
+ emptyList := NetObjGroupList{}
+ result := emptyList.FindOne()
+
+ if result.ID != "" || result.Name != "" {
+ t.Fatal("Expected empty NetObjGroup, got:", result)
+ }
+}
+
+func TestFilterByIDNotFound(t *testing.T) {
+ actual := testNetObjGroups.FilterByID("0b16493b-0a38-4c90-80a0-c19f898f593n")
+
+ if len(actual.Objects) != 0 {
+ t.Fatal("Expected 0 policies, found:", len(actual.Objects))
+ }
+}
+
+func TestFilterByDisplayNameNotFound(t *testing.T) {
+ actual := testNetObjGroups.FilterByName("Nonexistent Policy")
+
+ if len(actual.Objects) != 0 {
+ t.Fatal("Expected 0 policies, found:", len(actual.Objects))
+ }
+}
diff --git a/pkg/sdn/netobjgroups/get.go b/pkg/sdn/netobjgroups/get.go
new file mode 100644
index 0000000..130dcfe
--- /dev/null
+++ b/pkg/sdn/netobjgroups/get.go
@@ -0,0 +1,47 @@
+package netobjgroups
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// GetRequest struct to get info about a network object group
+type GetRequest struct {
+ // ID of a network object group
+ // Required: true
+ NetObjGroupID string `url:"object_group_id" json:"object_group_id" validate:"required"`
+}
+
+// Get gets network object group details as a NetworkObjectGroups struct
+func (nog NetworkObjectGroups) Get(ctx context.Context, req GetRequest) (*RecordNetObjGroup, error) {
+ res, err := nog.GetRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RecordNetObjGroup{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+
+}
+
+// GetRaw gets network object group details as an array of bytes
+func (nog NetworkObjectGroups) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/network_object_group/get"
+
+ res, err := nog.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/netobjgroups/list.go b/pkg/sdn/netobjgroups/list.go
new file mode 100644
index 0000000..6c397e3
--- /dev/null
+++ b/pkg/sdn/netobjgroups/list.go
@@ -0,0 +1,84 @@
+package netobjgroups
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// ListRequest struct to get a list of network object groups
+type ListRequest struct {
+ // Filter by name
+ // Required: false
+ Name string `url:"name,omitempty" json:"name,omitempty"`
+
+ // Filter by access group ID
+ // Required: false
+ AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,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, 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 (nog NetworkObjectGroups) List(ctx context.Context, req ListRequest) (*NetObjGroupList, error) {
+ res, err := nog.ListRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ objects := []RecordNetObjGroup{}
+
+ err = json.Unmarshal(res, &objects)
+ if err != nil {
+ return nil, err
+ }
+
+ result := NetObjGroupList{Objects: objects}
+
+ return &result, nil
+}
+
+// ListRaw gets a list of all address pools as an array of bytes
+func (nog NetworkObjectGroups) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
+
+ if err := validators.ValidateRequest(req); err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/network_object_group/list"
+
+ res, err := nog.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/netobjgroups/models.go b/pkg/sdn/netobjgroups/models.go
new file mode 100644
index 0000000..b7fbdd3
--- /dev/null
+++ b/pkg/sdn/netobjgroups/models.go
@@ -0,0 +1,702 @@
+package netobjgroups
+
+// Information about a version
+type RecordVersion struct {
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// List of network object groups
+type NetObjGroupList struct {
+ Objects []RecordNetObjGroup
+}
+
+// Main info about a network object group
+type RecordNetObjGroup struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Counters
+ Counters Counter `json:"counters"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // External network ports attached to a network object group
+ ExternalNetworkPorts ExternalNetworkPorts `json:"external_network_ports"`
+
+ // ID of a network object group
+ ID string `json:"id"`
+
+ // Logical ports attached to a network object group
+ LogicalPorts LogicalPorts `json:"logical_ports"`
+
+ // Name of a network object group
+ Name string `json:"name"`
+
+ // Security policies of a network object group
+ SecurityPolicies SecurityPolicies `json:"security_policies"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Info about counters
+type Counter struct {
+ // Amount of logical ports
+ LogicalPortsCount uint64 `json:"logical_ports_count"`
+
+ // Amount of security policies
+ SecurityPoliciesCount uint64 `json:"security_policies_count"`
+
+ // Amount of security rules
+ SecurityRulesCount uint64 `json:"security_rules_count"`
+}
+
+// List of counters
+type Counters []Counter
+
+// Info about an external network port
+type ExternalNetworkPort struct {
+ // Bridge network name
+ BridgeNetworkName string `json:"bridge_network_name"`
+
+ // Default gateaway for IPv4
+ DefaultGateawayIPv4 string `json:"default_gateaway_ipv4"`
+
+ // Default gateaway for IPv6
+ DefaultGateawayIPv6 string `json:"default_gateaway_ipv6"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Details of external network ports
+ ExternalNetworkPorts ExternalNetworkPortsField `json:"external_network_ports"`
+
+ // Hypervisors
+ Hypervisors []string `json:"hypervisors"`
+
+ // ID of an external network port
+ ID string `json:"id"`
+
+ // Status
+ Status Status `json:"status"`
+
+ // Version ID
+ VersionID string `json:"version_id"`
+
+ // Subnet for V4
+ SubnetV4 string `json:"subnet_v4"`
+
+ // Subnet for V6
+ SubnetV6 string `json:"subnet_v6"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // VLAN Tag
+ VLANTag string `json:"vlan_tag"`
+}
+
+// List of external network ports
+type ExternalNetworkPorts []ExternalNetworkPort
+
+// Info about a logical port
+type LogicalPort struct {
+ // ID
+ ID string `json:"id"`
+
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Adapter MAC
+ AdapterMAC string `json:"adapter_mac"`
+
+ // Is address detected
+ AddressDetection bool `json:"address_detection"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Is a logical port enabled
+ Enabled bool `json:"enabled"`
+
+ // External network ID
+ ExternalNetworkID string `json:"external_network_id"`
+
+ // Hypervisor
+ Hypervisor string `json:"hypervisor"`
+
+ // Hypervisor display name
+ HypervisorDisplayName string `json:"hypervisor_display_name"`
+
+ // Live migration target HV
+ LiveMigrationTargetHV string `json:"live_migration_target_hv"`
+
+ // Status
+ Status Status `json:"status"`
+
+ // Bindings
+ Bindings Bindings `json:"bindings"`
+
+ // Unique identifier
+ UniqueIdentifier string `json:"unique_identifier"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// List a logical ports
+type LogicalPorts []LogicalPort
+
+// Info about a security policy
+type SecurityPolicy struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Applied to net object group ID
+ AppliedToNetObjectGroupID string `json:"applied_to_net_object_group_id"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Is a security policy enabled
+ Enabled bool `json:"enabled"`
+
+ // End priority
+ EndPriority uint64 `json:"end_priority"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Security rules
+ SecurityRules SecurityRules `json:"security_rules"`
+
+ // Start priority
+ StartPriority uint64 `json:"start_priority"`
+
+ // Status
+ Status Status `json:"status"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+}
+
+// List of security policies
+type SecurityPolicies []SecurityPolicy
+
+// Details of external network ports field
+type ExternalNetworkPortField struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ Comment string `json:"comment"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Is a security policy enabled
+ Enabled bool `json:"enabled"`
+
+ // IP v4
+ IPv4 string `json:"ipv4"`
+
+ // IP v6
+ IPv6 string `json:"ipv6"`
+
+ // Config for IP v6
+ IPv6Config IPv6Config `json:"ipv6_config"`
+
+ // MAC
+ MAC string `json:"mac"`
+
+ // Info about a router gateaway port
+ RouterGateawayPort RouterGateawayPort `json:"router_gateaway_port"`
+
+ // Info about a floating IP
+ FloatingIP FloatingIP `json:"floating_ip"`
+}
+
+// List of external network ports fields
+type ExternalNetworkPortsField []ExternalNetworkPortField
+
+// Info about a status
+type Status struct {
+ // Common
+ Common string `json:"common"`
+
+ // Info about hypervisors
+ Hypervisors HypervisorsInfo `json:"hypervisors"`
+}
+
+// Config for IP v6
+type IPv6Config struct {
+ // Address mode
+ AddressMode string `json:"address_mode"`
+
+ // Is periodic RA enabled
+ EnablePeriodicRA bool `json:"enable_periodic_ra"`
+
+ // Interval RA
+ IntervalRA uint64 `json:"interval_ra"`
+
+ // Router preference
+ RouterPreference string `json:"router_preference"`
+}
+
+// Info about a router gateaway port
+type RouterGateawayPort struct {
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Router display name
+ RouterDisplayName string `json:"router_display_name"`
+
+ // Router ID
+ RouterID string `json:"router_id"`
+
+ // Is SNAT enabled
+ SNATEnabled bool `json:"snat_enabled"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+}
+
+// Info about a floating IP
+type FloatingIP struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // External network port in floating IP
+ ExternalNetworkPort ExternalNetworkPortFieldInFloatingIP `json:"external_network_port"`
+
+ // Logical port
+ LogicalPort LogicalPort `json:"logical_port"`
+
+ // Router
+ Router Router `json:"router"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// TODO later
+type ExternalNetworkPortFieldInFloatingIP struct{}
+
+// Info about a router
+type Router struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Is a security policy enabled
+ Enabled bool `json:"enabled"`
+
+ // Gateaway ports
+ GateawayPorts GateawayPorts `json:"gateaway_ports"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Policies
+ Policies Policies `json:"policies"`
+
+ // Port
+ Port Ports `json:"ports"`
+
+ // Status
+ Status Status `json:"status"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Info about bindings
+type Bindings struct {
+ // ID
+ ID string `json:"id"`
+
+ // Segment display name
+ SegmentDisplayName string `json:"segment_display_name"`
+
+ // Segment ID
+ SegmentID string `json:"segment_id"`
+
+ // Is a port secured
+ PortSecurity bool `json:"port_security"`
+
+ // Is an address detected
+ AddressDetection bool `json:"address_detection"`
+
+ // Is excluded from firewall
+ IsExcludedFromFirewall bool `json:"is_excluded_from_firewall"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Logical port addresses
+ LogicalPortAddresses LogicalPortAddresses `json:"logical_port_addresses"`
+}
+
+// Info about a gateaway port
+type GateawayPort struct {
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Max external L4 port
+ ExternalL4PortMax uint64 `json:"external_l4_port_max"`
+
+ // Min external L4 port
+ ExternalL4PortMin uint64 `json:"external_l4_port_min"`
+
+ // External network port in floating IP
+ ExternalNetworkPortFieldInFloatingIP ExternalNetworkPortFieldInFloatingIP `json:"external_network_port"` // to check
+
+ // ID
+ ID string `json:"id"`
+
+ // Is SNAT enabled
+ SNATEnabled bool `json:"snat_enabled"`
+
+ // Status
+ Status Status `json:"status"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// List of gateaway ports
+type GateawayPorts []GateawayPort
+
+// Info about a policy
+type Policy struct {
+ // Action
+ Action string `json:"action"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Is a security policy enabled
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Match
+ Match string `json:"match"`
+
+ // List of next IP v4 addresses
+ NextIPv4Address []string `json:"next_ipv4_address"`
+
+ // List of next IP v6 addresses
+ NextIPv6Address []string `json:"next_ipv6_address"`
+
+ // Priority
+ Priority int `json:"priority"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// List of policies
+type Policies []Policy
+
+// Info about a port
+type Port struct {
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Is a security policy enabled
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // IPv4 address
+ IPv4Address string `json:"ipv4_address"`
+
+ // IPv6 address
+ IPv6Address string `json:"ipv6_address"`
+
+ // Config for IPv6
+ IPv6Config IPv6Config `json:"ipv6_config"`
+
+ // MAC
+ MAC string `json:"mac"`
+
+ // Segment
+ Segment Segment `json:"segment"`
+
+ // Segment ID
+ SegmentID string `json:"segment_id"`
+
+ // Status
+ Status Status `json:"status"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // VersionID
+ VersionID uint64 `json:"version_id"`
+}
+
+// List of ports
+type Ports []Port
+
+// Info about a segment
+type Segment struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Created at
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ DHCPv4 DHCPv4 `json:"dhcp_v4"`
+ DHCPv6 DHCPv6 `json:"dhcp_v6"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Is a security policy enabled
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Info about logical ports
+ LogicalPortsInfo LogicalPortsInfo `json:"logical_ports_info"`
+
+ // Info about routers
+ RoutersInfo RoutersInfo `json:"routers_info"`
+
+ // Status
+ Status Status `json:"status"`
+
+ // Subnet v4
+ SubnetV4 string `json:"subnet_v4"`
+
+ // Subnet v6
+ SubnetV6 string `json:"subnet_v6"`
+
+ // Updated at
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Info about DHCP v4
+type DHCPv4 struct {
+ // List of DNS
+ DNS []string `json:"dns"`
+
+ // List of excluded address ranges
+ ExcludedAddressRanges []string `json:"excluded_address_ranges"`
+
+ // Gateaway
+ Gateaway string `json:"gateaway"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Lease time
+ LeaseTime uint64 `json:"lease_time"`
+
+ // Server IP
+ ServerIP string `json:"server_ip"`
+
+ // is there a server MAC
+ ServerMAC bool `json:"server_mac"`
+
+ // Is a security policy enabled
+ Enabled bool `json:"enabled"`
+}
+
+// Info about DHCP v6
+type DHCPv6 struct {
+ // Address prefix
+ AddressPrefix string `json:"address_prefix"`
+
+ // List of DNS
+ DNS []string `json:"dns"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Lease time
+ LeaseTime uint64 `json:"lease_time"`
+
+ // Server MAC
+ ServerMAC string `json:"server_mac"`
+
+ // Is a security policy enabled
+ Enabled bool `json:"enabled"`
+}
+
+// Info about logical ports
+type LogicalPortsInfo struct {
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // ID
+ ID string `json:"id"`
+}
+
+// Info about routers
+type RoutersInfo struct {
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // ID
+ ID string `json:"id"`
+}
+
+// Info about a hypervisor
+type HypervisorInfo 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"`
+
+ // Synced at
+ SyncedAt string `json:"synced_at"`
+}
+
+// List of hypervisors info
+type HypervisorsInfo []HypervisorInfo
+
+type LogicalPortAddress struct {
+ // IP
+ IP string `json:"ip"`
+
+ // IP Type
+ IPType string `json:"ip_type"`
+
+ // Is logical port address discovered
+ IsDiscovered bool `json:"is_discovered"`
+
+ // Is logical port address 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 at
+ AssignedAt string `json:"assigned_at"`
+}
+
+// List of logical port addresses
+type LogicalPortAddresses []LogicalPortAddress
+
+// TODO
+type SecurityRule struct{}
+
+type SecurityRules []SecurityRule
diff --git a/pkg/sdn/netobjgroups/network_object_groups.go b/pkg/sdn/netobjgroups/network_object_groups.go
new file mode 100644
index 0000000..a8293a3
--- /dev/null
+++ b/pkg/sdn/netobjgroups/network_object_groups.go
@@ -0,0 +1,18 @@
+// API Actor API for managing SDN network object groups
+package netobjgroups
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
+)
+
+// Structure for creating request to network object groups
+type NetworkObjectGroups struct {
+ client interfaces.Caller
+}
+
+// Builder for external network object groups
+func New(client interfaces.Caller) *NetworkObjectGroups {
+ return &NetworkObjectGroups{
+ client,
+ }
+}
diff --git a/pkg/sdn/netobjgroups/update.go b/pkg/sdn/netobjgroups/update.go
new file mode 100644
index 0000000..d4fa884
--- /dev/null
+++ b/pkg/sdn/netobjgroups/update.go
@@ -0,0 +1,57 @@
+package netobjgroups
+
+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 a network object group
+type UpdateRequest struct {
+ // ID of the object group
+ // Required: true
+ ObjectGroupID string `url:"object_group_id" json:"object_group_id" validate:"required"`
+
+ // ID of the version
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // Access Group ID
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // Description of the network object group
+ // Required: true
+ Description string `url:"description" json:"description" validate:"required"`
+
+ // Name of the network object group
+ // Required: true
+ Name string `url:"name" json:"name" validate:"required"`
+}
+
+// Update updates a network object group
+func (nog NetworkObjectGroups) Update(ctx context.Context, req UpdateRequest) (*RecordNetObjGroup, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/network_object_group/update"
+
+ res, err := nog.client.DecortApiCallCtype(ctx, http.MethodPut, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RecordNetObjGroup{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/network_object_groups.go b/pkg/sdn/network_object_groups.go
new file mode 100644
index 0000000..ed91bce
--- /dev/null
+++ b/pkg/sdn/network_object_groups.go
@@ -0,0 +1,10 @@
+package sdn
+
+import (
+ nog "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/netobjgroups"
+)
+
+// Accessing the SDN method group
+func (sdn *SDN) NetworkObjectGroups() *nog.NetworkObjectGroups {
+ return nog.New(sdn.client)
+}
diff --git a/pkg/sdn/routers.go b/pkg/sdn/routers.go
new file mode 100644
index 0000000..71f2913
--- /dev/null
+++ b/pkg/sdn/routers.go
@@ -0,0 +1,10 @@
+package sdn
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/routers"
+)
+
+// Accessing the SDN method group
+func (sdn *SDN) Routers() *routers.Routers {
+ return routers.New(sdn.client)
+}
diff --git a/pkg/sdn/routers/create.go b/pkg/sdn/routers/create.go
new file mode 100644
index 0000000..93d6d05
--- /dev/null
+++ b/pkg/sdn/routers/create.go
@@ -0,0 +1,53 @@
+package routers
+
+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 router
+type CreateRequest struct {
+ // Access group ID
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // Description
+ // Required: true
+ Description string `url:"description" json:"description" validate:"required"`
+
+ // Name of acces group
+ // Required: true
+ DisplayName string `url:"display_name" json:"display_name" validate:"required"`
+
+ // Enabled True or False
+ // Required: true
+ Enabled interface{} `url:"enabled" json:"enabled" validate:"required,isBool"`
+}
+
+// Create creates a access groups
+func (i Routers) Create(ctx context.Context, req CreateRequest) (*RoutersModel, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/create"
+
+ res, err := i.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RoutersModel{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/routers/delete.go b/pkg/sdn/routers/delete.go
new file mode 100644
index 0000000..01c0a26
--- /dev/null
+++ b/pkg/sdn/routers/delete.go
@@ -0,0 +1,42 @@
+package routers
+
+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 router
+type DeleteRequest struct {
+ // ID of router
+ // Required: true
+ RouterID string `url:"router_id" json:"router_id" validate:"required"`
+
+ // ID of version
+ // 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 delete a router
+func (e Routers) Delete(ctx context.Context, req DeleteRequest) error {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/delete"
+
+ _, err = e.client.DecortApiCallCtype(ctx, http.MethodDelete, url, constants.MIMEJSON, req)
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/sdn/routers/filter.go b/pkg/sdn/routers/filter.go
new file mode 100644
index 0000000..508a3dd
--- /dev/null
+++ b/pkg/sdn/routers/filter.go
@@ -0,0 +1,42 @@
+package routers
+
+// FilterByID returns RoutersList with specified ID.
+func (agl RoutersList) FilterByID(id string) RoutersList {
+ predicate := func(ia RoutersModel) bool {
+ return ia.ID == id
+ }
+
+ return agl.FilterFunc(predicate)
+}
+
+// FilterByName returns RoutersList with specified Name.
+func (agl RoutersList) FilterByName(name string) RoutersList {
+ predicate := func(ia RoutersModel) bool {
+ return ia.DisplayName == name
+ }
+
+ return agl.FilterFunc(predicate)
+}
+
+// FilterFunc allows filtering RoutersList based on a user-specified predicate.
+func (agl RoutersList) FilterFunc(predicate func(RoutersModel) bool) RoutersList {
+ var result RoutersList
+
+ for _, acc := range agl {
+ if predicate(acc) {
+ result = append(result, acc)
+ }
+ }
+
+ return result
+}
+
+// FindOne returns first element.
+// If none was found, returns an empty struct.
+func (agl RoutersList) FindOne() RoutersModel {
+ if len(agl) == 0 {
+ return RoutersModel{}
+ }
+
+ return agl[0]
+}
diff --git a/pkg/sdn/routers/filter_test.go b/pkg/sdn/routers/filter_test.go
new file mode 100644
index 0000000..8dff8a6
--- /dev/null
+++ b/pkg/sdn/routers/filter_test.go
@@ -0,0 +1,309 @@
+package routers
+
+import (
+ "testing"
+)
+
+var testRoutersList = RoutersList{
+ {
+ ID: "router1",
+ DisplayName: "DevelopersRouter",
+ Description: "First router",
+ CreatedAt: "2023-01-01",
+ AccessGroupID: "group1",
+ AccessGroupName: "Developers",
+ Enabled: true,
+ GatewayPorts: []GatewayPort{
+ {
+ ID: "gateway1",
+ Description: "Gateway port 1",
+ SNATEnabled: true,
+ ExternalL4PortMin: 1000,
+ ExternalL4PortMax: 2000,
+ CreatedAt: "2023-01-01",
+ UpdatedAt: "2023-01-01",
+ VersionID: 1,
+ ExternalNetworkPort: ExternalNetworkPort{
+ IPv4: "192.168.1.1",
+ IPv6: "2001:db8::1",
+ },
+ },
+ },
+ Policies: []Policy{
+ {
+ ID: "policy1",
+ DisplayName: "Policy1",
+ Action: "allow",
+ Priority: 1,
+ Enabled: true,
+ },
+ },
+ Ports: []Port{
+ {
+ ID: "port1",
+ Description: "Port 1",
+ Enabled: true,
+ IPv4Address: "10.0.0.1",
+ },
+ },
+ Status: Status{
+ Common: "active",
+ },
+ VersionID: 1,
+ },
+ {
+ ID: "router2",
+ DisplayName: "AdminsRouter",
+ Description: "Second router",
+ CreatedAt: "2023-01-02",
+ AccessGroupID: "group2",
+ AccessGroupName: "Admins",
+ Enabled: true,
+ GatewayPorts: []GatewayPort{
+ {
+ ID: "gateway2",
+ Description: "Gateway port 2",
+ SNATEnabled: false,
+ ExternalL4PortMin: 3000,
+ ExternalL4PortMax: 4000,
+ CreatedAt: "2023-01-02",
+ UpdatedAt: "2023-01-02",
+ VersionID: 1,
+ ExternalNetworkPort: ExternalNetworkPort{
+ IPv4: "192.168.1.2",
+ IPv6: "2001:db8::2",
+ },
+ },
+ },
+ Policies: []Policy{
+ {
+ ID: "policy2",
+ DisplayName: "Policy2",
+ Action: "deny",
+ Priority: 2,
+ Enabled: true,
+ },
+ },
+ Ports: []Port{
+ {
+ ID: "port2",
+ Description: "Port 2",
+ Enabled: true,
+ IPv4Address: "10.0.0.2",
+ },
+ },
+ Status: Status{
+ Common: "active",
+ },
+ VersionID: 2,
+ },
+ {
+ ID: "router3",
+ DisplayName: "UsersRouter",
+ Description: "Third router",
+ CreatedAt: "2023-01-03",
+ AccessGroupID: "group3",
+ AccessGroupName: "Users",
+ Enabled: false,
+ GatewayPorts: []GatewayPort{
+ {
+ ID: "gateway3",
+ Description: "Gateway port 3",
+ SNATEnabled: true,
+ ExternalL4PortMin: 5000,
+ ExternalL4PortMax: 6000,
+ CreatedAt: "2023-01-03",
+ UpdatedAt: "2023-01-03",
+ VersionID: 1,
+ ExternalNetworkPort: ExternalNetworkPort{
+ IPv4: "192.168.1.3",
+ IPv6: "2001:db8::3",
+ },
+ },
+ },
+ Policies: []Policy{
+ {
+ ID: "policy3",
+ DisplayName: "Policy3",
+ Action: "allow",
+ Priority: 3,
+ Enabled: false,
+ },
+ },
+ Ports: []Port{
+ {
+ ID: "port3",
+ Description: "Port 3",
+ Enabled: false,
+ IPv4Address: "10.0.0.3",
+ },
+ },
+ Status: Status{
+ Common: "inactive",
+ },
+ VersionID: 3,
+ },
+}
+
+func TestFilterByID(t *testing.T) {
+ actual := testRoutersList.FilterByID("router2").FindOne()
+
+ if actual.ID != "router2" {
+ t.Fatal("actual:", actual.ID, "> expected: router2")
+ }
+}
+
+func TestFilterByName(t *testing.T) {
+ actual := testRoutersList.FilterByName("UsersRouter").FindOne()
+
+ if actual.DisplayName != "UsersRouter" {
+ t.Fatal("actual:", actual.DisplayName, ">> expected: UsersRouter")
+ }
+}
+
+func TestFilterFunc(t *testing.T) {
+ actual := testRoutersList.FilterFunc(func(rm RoutersModel) bool {
+ return rm.Description == "Second router"
+ })
+
+ if len(actual) != 1 || actual[0].ID != "router2" {
+ t.Fatal("Expected 1 router with description 'Second router', found:", len(actual))
+ }
+}
+
+func TestFindOneWithResults(t *testing.T) {
+ result := testRoutersList.FilterByID("router1").FindOne()
+ if result.ID != "router1" {
+ t.Fatal("Expected router1, got:", result.ID)
+ }
+}
+
+func TestFindOneEmpty(t *testing.T) {
+ emptyList := RoutersList{}
+ result := emptyList.FindOne()
+
+ if result.ID != "" || result.DisplayName != "" {
+ t.Fatal("Expected empty RoutersModel, got:", result)
+ }
+}
+
+func TestFilterByIDNotFound(t *testing.T) {
+ actual := testRoutersList.FilterByID("nonexistent")
+
+ if len(actual) != 0 {
+ t.Fatal("Expected 0 routers, found:", len(actual))
+ }
+}
+
+func TestFilterByNameNotFound(t *testing.T) {
+ actual := testRoutersList.FilterByName("Nonexistent Router")
+
+ if len(actual) != 0 {
+ t.Fatal("Expected 0 routers, found:", len(actual))
+ }
+}
+
+func TestFilterByEnabledStatus(t *testing.T) {
+ actual := testRoutersList.FilterFunc(func(rm RoutersModel) bool {
+ return rm.Enabled
+ })
+
+ if len(actual) != 2 {
+ t.Fatal("Expected 2 enabled routers, found:", len(actual))
+ }
+}
+
+func TestFilterByAccessGroup(t *testing.T) {
+ actual := testRoutersList.FilterFunc(func(rm RoutersModel) bool {
+ return rm.AccessGroupName == "Developers"
+ })
+
+ if len(actual) != 1 || actual[0].ID != "router1" {
+ t.Fatal("Expected 1 router with Developers access group, found:", len(actual))
+ }
+}
+
+func TestFilterByPolicyAction(t *testing.T) {
+ actual := testRoutersList.FilterFunc(func(rm RoutersModel) bool {
+ for _, policy := range rm.Policies {
+ if policy.Action == "deny" {
+ return true
+ }
+ }
+ return false
+ })
+
+ if len(actual) != 1 || actual[0].ID != "router2" {
+ t.Fatal("Expected 1 router with deny policy, found:", len(actual))
+ }
+}
+
+func TestFilterByGatewayPortRange(t *testing.T) {
+ actual := testRoutersList.FilterFunc(func(rm RoutersModel) bool {
+ for _, gateway := range rm.GatewayPorts {
+ if gateway.ExternalL4PortMin >= 3000 && gateway.ExternalL4PortMax <= 4000 {
+ return true
+ }
+ }
+ return false
+ })
+
+ if len(actual) != 1 || actual[0].ID != "router2" {
+ t.Fatal("Expected 1 router with gateway port range 3000-4000, found:", len(actual))
+ }
+}
+
+func TestFilterBySNATEnabled(t *testing.T) {
+ actual := testRoutersList.FilterFunc(func(rm RoutersModel) bool {
+ for _, gateway := range rm.GatewayPorts {
+ if gateway.SNATEnabled {
+ return true
+ }
+ }
+ return false
+ })
+
+ if len(actual) != 2 {
+ t.Fatal("Expected 2 routers with SNAT enabled, found:", len(actual))
+ }
+}
+
+func TestFilterByStatus(t *testing.T) {
+ actual := testRoutersList.FilterFunc(func(rm RoutersModel) bool {
+ return rm.Status.Common == "inactive"
+ })
+
+ if len(actual) != 1 || actual[0].ID != "router3" {
+ t.Fatal("Expected 1 router with inactive status, found:", len(actual))
+ }
+}
+
+func TestFilterByPortEnabled(t *testing.T) {
+ actual := testRoutersList.FilterFunc(func(rm RoutersModel) bool {
+ for _, port := range rm.Ports {
+ if port.Enabled {
+ return true
+ }
+ }
+ return false
+ })
+
+ if len(actual) != 2 {
+ t.Fatal("Expected 2 routers with enabled ports, found:", len(actual))
+ }
+}
+
+func TestFilterByPolicyPriority(t *testing.T) {
+ actual := testRoutersList.FilterFunc(func(rm RoutersModel) bool {
+ for _, policy := range rm.Policies {
+ if policy.Priority > 2 {
+ return true
+ }
+ }
+ return false
+ })
+
+ if len(actual) != 1 || actual[0].ID != "router3" {
+ t.Fatal("Expected 1 router with policy priority > 2, found:", len(actual))
+ }
+}
diff --git a/pkg/sdn/routers/gateaway_port.go b/pkg/sdn/routers/gateaway_port.go
new file mode 100644
index 0000000..0b22257
--- /dev/null
+++ b/pkg/sdn/routers/gateaway_port.go
@@ -0,0 +1,10 @@
+package routers
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/routers/gwport"
+)
+
+// Accessing the routers gateway port method group
+func (r *Routers) GWPort() *gwport.GWPort {
+ return gwport.New(r.client)
+}
diff --git a/pkg/sdn/routers/get.go b/pkg/sdn/routers/get.go
new file mode 100644
index 0000000..4b7e57c
--- /dev/null
+++ b/pkg/sdn/routers/get.go
@@ -0,0 +1,47 @@
+package routers
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// GetRequest struct to get information about router
+type GetRequest struct {
+ // ID
+ // Required: true
+ ID string `url:"router_id" json:"router_id" validate:"required"`
+}
+
+// Get gets routers details as a RoutersModel struct
+func (a Routers) Get(ctx context.Context, req GetRequest) (*RoutersModel, error) {
+ res, err := a.GetRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RoutersModel{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+
+}
+
+// GetRaw gets routers details as an array of bytes
+func (a Routers) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/get"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/routers/gwport/create.go b/pkg/sdn/routers/gwport/create.go
new file mode 100644
index 0000000..a580204
--- /dev/null
+++ b/pkg/sdn/routers/gwport/create.go
@@ -0,0 +1,57 @@
+package gwport
+
+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 gateway port
+type CreateRequest struct {
+ // ID of router
+ // Required: true
+ RouterID string `url:"router_id" json:"router_id" validate:"required"`
+
+ // ID of version
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // Detailed description of the external network
+ // Required: true
+ Description string `url:"description" json:"description" validate:"required"`
+
+ // External network port ID
+ // Required: true
+ ExternalNetworkPortID string `url:"external_network_port_id" json:"external_network_port_id" validate:"required"`
+
+ // Whether is enabled
+ // Required: true
+ Enabled bool `url:"snat_enabled" json:"snat_enabled"`
+}
+
+// Create creates a gateway port
+func (i GWPort) Create(ctx context.Context, req CreateRequest) (*GatewayPort, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/gateway_port/create"
+
+ res, err := i.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := GatewayPort{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/routers/gwport/delete.go b/pkg/sdn/routers/gwport/delete.go
new file mode 100644
index 0000000..78b77c7
--- /dev/null
+++ b/pkg/sdn/routers/gwport/delete.go
@@ -0,0 +1,46 @@
+package gwport
+
+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 router
+type DeleteRequest struct {
+ // ID of router
+ // Required: true
+ RouterID string `url:"router_id" json:"router_id" validate:"required"`
+
+ // ID of gateway port
+ // Required: true
+ GatewayPortID string `url:"gateway_port_id" json:"gateway_port_id" validate:"required"`
+
+ // ID of version
+ // 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 delete a router
+func (e GWPort) Delete(ctx context.Context, req DeleteRequest) error {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/gateway_port/delete"
+
+ _, err = e.client.DecortApiCallCtype(ctx, http.MethodDelete, url, constants.MIMEJSON, req)
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/sdn/routers/gwport/gateaway_port.go b/pkg/sdn/routers/gwport/gateaway_port.go
new file mode 100644
index 0000000..b5146f5
--- /dev/null
+++ b/pkg/sdn/routers/gwport/gateaway_port.go
@@ -0,0 +1,19 @@
+package gwport
+
+// API Actor API for managing SDN routers gateway port
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
+)
+
+// Structure for creating request to routers gateway port
+type GWPort struct {
+ client interfaces.Caller
+}
+
+// Builder for routers gateway port endpoints
+func New(client interfaces.Caller) *GWPort {
+ return &GWPort{
+ client,
+ }
+}
diff --git a/pkg/sdn/routers/gwport/get.go b/pkg/sdn/routers/gwport/get.go
new file mode 100644
index 0000000..814ab49
--- /dev/null
+++ b/pkg/sdn/routers/gwport/get.go
@@ -0,0 +1,51 @@
+package gwport
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// GetRequest struct to get information about gateway port
+type GetRequest struct {
+ // Router ID
+ // Required: true
+ RouterID string `url:"router_id" json:"router_id" validate:"required"`
+
+ // Gateway port ID
+ // Required: true
+ GatewayPortID string `url:"gateway_port_id" json:"gateway_port_id" validate:"required"`
+}
+
+// Get gets gateway port details as a GatewayPort struct
+func (a GWPort) Get(ctx context.Context, req GetRequest) (*GatewayPort, error) {
+ res, err := a.GetRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := GatewayPort{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+
+}
+
+// GetRaw gets gateway port details as an array of bytes
+func (a GWPort) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/gateway_port/get"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/routers/gwport/list.go b/pkg/sdn/routers/gwport/list.go
new file mode 100644
index 0000000..ff2f13a
--- /dev/null
+++ b/pkg/sdn/routers/gwport/list.go
@@ -0,0 +1,47 @@
+package gwport
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// ListRequest struct to get list of gateway ports
+type ListRequest struct {
+ // Router ID
+ // Required: true
+ RouterID string `url:"router_id" json:"router_id" validate:"required"`
+}
+
+// List gets gateway port list
+func (a GWPort) List(ctx context.Context, req ListRequest) (GatewayPortsList, error) {
+ res, err := a.ListRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := []GatewayPort{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return info, nil
+
+}
+
+// GetRaw gets gateway port list as an array of bytes
+func (a GWPort) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/gateway_port/list"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/routers/gwport/models.go b/pkg/sdn/routers/gwport/models.go
new file mode 100644
index 0000000..1ebcf66
--- /dev/null
+++ b/pkg/sdn/routers/gwport/models.go
@@ -0,0 +1,481 @@
+package gwport
+
+// List of ports
+type GatewayPortsList []GatewayPort
+
+// Gateway port information
+type GatewayPort struct {
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // External L4 port maximum
+ ExternalL4PortMax int `json:"external_l4_port_max"`
+
+ // External L4 port minimum
+ ExternalL4PortMin int `json:"external_l4_port_min"`
+
+ // External network port
+ ExternalNetworkPort ExternalNetworkPort `json:"external_network_port"`
+
+ // ID
+ ID string `json:"id"`
+
+ // SNAT enabled flag
+ SNATEnabled bool `json:"snat_enabled"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// External network port information
+type ExternalNetworkPort struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Comment
+ Comment string `json:"comment"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // IPv4 address
+ IPv4 string `json:"ipv4"`
+
+ // IPv6 address
+ IPv6 string `json:"ipv6"`
+
+ // IPv6 configuration
+ 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"`
+}
+
+// IPv6 configuration information
+type IPv6Config struct {
+ // Address mode
+ AddressMode string `json:"address_mode"`
+
+ // Enable periodic RA flag
+ EnablePeriodicRA bool `json:"enable_periodic_ra"`
+
+ // Interval RA
+ IntervalRA int `json:"interval_ra"`
+
+ // Router preference
+ RouterPreference string `json:"router_preference"`
+}
+
+// Router gateway port information
+type RouterGatewayPort struct {
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Router display name
+ RouterDisplayName string `json:"router_display_name"`
+
+ // Router ID
+ RouterID string `json:"router_id"`
+
+ // SNAT enabled flag
+ SNATEnabled bool `json:"snat_enabled"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+}
+
+// Floating IP information
+type FloatingIP 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"`
+
+ // External network port
+ ExternalNetworkPort string `json:"external_network_port"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Logical port
+ LogicalPort LogicalPort `json:"logical_port"`
+
+ // Router
+ Router string `json:"router"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Logical port information
+type LogicalPort struct {
+ // ID
+ ID string `json:"id"`
+
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Adapter MAC
+ AdapterMAC string `json:"adapter_mac"`
+
+ // Address detection flag
+ AddressDetection bool `json:"address_detection"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // External network ID
+ ExternalNetworkID string `json:"external_network_id"`
+
+ // Hypervisor
+ Hypervisor string `json:"hypervisor"`
+
+ // Hypervisor display name
+ HypervisorDisplayName string `json:"hypervisor_display_name"`
+
+ // Live migration target HV
+ LiveMigrationTargetHV string `json:"live_migration_target_hv"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // Bindings information
+ Bindings Bindings `json:"bindings"`
+
+ // Unique identifier
+ UniqueIdentifier string `json:"unique_identifier"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Status information
+type Status struct {
+ // Common status
+ Common string `json:"common"`
+
+ // Hypervisors status list
+ 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"`
+
+ // Synced at time
+ SyncedAt string `json:"synced_at"`
+}
+
+// Bindings information
+type Bindings struct {
+ // ID
+ ID string `json:"id"`
+
+ // Segment display name
+ SegmentDisplayName string `json:"segment_display_name"`
+
+ // Segment ID
+ SegmentID string `json:"segment_id"`
+
+ // Port security flag
+ PortSecurity bool `json:"port_security"`
+
+ // Address detection flag
+ AddressDetection bool `json:"address_detection"`
+
+ // Is excluded from firewall flag
+ IsExcludedFromFirewall bool `json:"is_excluded_from_firewall"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Logical port addresses list
+ LogicalPortAddresses []LogicalPortAddress `json:"logical_port_addresses"`
+}
+
+// Logical port address information
+type LogicalPortAddress struct {
+ // IP address
+ IP string `json:"ip"`
+
+ // IP type
+ IPType string `json:"ip_type"`
+
+ // Is discovered flag
+ IsDiscovered bool `json:"is_discovered"`
+
+ // Is primary flag
+ IsPrimary bool `json:"is_primary"`
+
+ // MAC address
+ MAC string `json:"mac"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Logical port ID
+ LogicalPortID string `json:"logical_port_id"`
+
+ // Assigned at time
+ AssignedAt string `json:"assigned_at"`
+}
+
+// Policy information
+type Policy struct {
+ // Action
+ Action string `json:"action"`
+
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Match criteria
+ Match map[string]interface{} `json:"match"`
+
+ // Next IPv4 addresses list
+ NextIPv4Address []string `json:"next_ipv4_address"`
+
+ // Next IPv6 addresses list
+ NextIPv6Address []string `json:"next_ipv6_address"`
+
+ // Priority
+ Priority int `json:"priority"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Port information
+type Port struct {
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // IPv4 address
+ IPv4Address string `json:"ipv4_address"`
+
+ // IPv6 address
+ IPv6Address string `json:"ipv6_address"`
+
+ // IPv6 configuration
+ IPv6Config IPv6Config `json:"ipv6_config"`
+
+ // MAC address
+ MAC string `json:"mac"`
+
+ // Segment information
+ Segment Segment `json:"segment"`
+
+ // Segment ID
+ SegmentID string `json:"segment_id"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Segment information
+type Segment 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"`
+
+ // Description
+ Description string `json:"description"`
+
+ // DHCPv4 configuration
+ DHCPv4 DHCPv4 `json:"dhcp_v4"`
+
+ // DHCPv6 configuration
+ DHCPv6 DHCPv6 `json:"dhcp_v6"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Logical ports info list
+ LogicalPortsInfo []LogicalPortInfo `json:"logical_ports_info"`
+
+ // Routers info list
+ RoutersInfo []RouterInfo `json:"routers_info"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // IPv4 subnet
+ SubnetV4 string `json:"subnet_v4"`
+
+ // IPv6 subnet
+ SubnetV6 string `json:"subnet_v6"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// DHCPv4 configuration
+type DHCPv4 struct {
+ // DNS servers list
+ DNS []string `json:"dns"`
+
+ // Excluded address ranges list
+ ExcludedAddressRanges []string `json:"excluded_address_ranges"`
+
+ // Gateway address
+ Gateway string `json:"gateway"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Lease time
+ LeaseTime int `json:"lease_time"`
+
+ // Server IP
+ ServerIP string `json:"server_ip"`
+
+ // Server MAC
+ ServerMAC string `json:"server_mac"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+}
+
+// DHCPv6 configuration
+type DHCPv6 struct {
+ // Address prefix
+ AddressPrefix string `json:"address_prefix"`
+
+ // DNS servers list
+ DNS []string `json:"dns"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Lease time
+ LeaseTime int `json:"lease_time"`
+
+ // Server MAC
+ ServerMAC string `json:"server_mac"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+}
+
+// Logical port info
+type LogicalPortInfo struct {
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // ID
+ ID string `json:"id"`
+}
+
+// Router info
+type RouterInfo struct {
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // ID
+ ID string `json:"id"`
+}
diff --git a/pkg/sdn/routers/gwport/update.go b/pkg/sdn/routers/gwport/update.go
new file mode 100644
index 0000000..8c1c189
--- /dev/null
+++ b/pkg/sdn/routers/gwport/update.go
@@ -0,0 +1,61 @@
+package gwport
+
+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 gateway port
+type UpdateRequest struct {
+ // ID of router
+ // Required: true
+ RouterID string `url:"router_id" json:"router_id" validate:"required"`
+
+ // ID of gateway port
+ // Required: true
+ GatewayPortID string `url:"gateway_port_id" json:"gateway_port_id" validate:"required"`
+
+ // ID of version
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // Detailed description of the external network
+ // Required: true
+ Description string `url:"description" json:"description" validate:"required"`
+
+ // External network port ID
+ // Required: true
+ ExternalNetworkPortID string `url:"external_network_port_id" json:"external_network_port_id" validate:"required"`
+
+ // Whether is enabled
+ // Required: true
+ Enabled bool `url:"snat_enabled" json:"snat_enabled"`
+}
+
+// Updated update a gateway port
+func (i GWPort) Update(ctx context.Context, req UpdateRequest) (*GatewayPort, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/gateway_port/update"
+
+ res, err := i.client.DecortApiCallCtype(ctx, http.MethodPut, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := GatewayPort{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/routers/ids.go b/pkg/sdn/routers/ids.go
new file mode 100644
index 0000000..d2fc6bd
--- /dev/null
+++ b/pkg/sdn/routers/ids.go
@@ -0,0 +1,10 @@
+package routers
+
+// IDs gets array of IDs from RoutersList struct
+func (rl RoutersList) IDs() []string {
+ res := make([]string, 0, len(rl))
+ for _, c := range rl {
+ res = append(res, c.ID)
+ }
+ return res
+}
diff --git a/pkg/sdn/routers/list.go b/pkg/sdn/routers/list.go
new file mode 100644
index 0000000..a50a81d
--- /dev/null
+++ b/pkg/sdn/routers/list.go
@@ -0,0 +1,90 @@
+package routers
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// ListRequest struct to get a list of routers
+type ListRequest struct {
+ // Filter by display name
+ // Required: false
+ DisplayName string `url:"display_name,omitempty" json:"display_name,omitempty"`
+
+ // Filter by enabled status
+ // Required: false
+ Enabled interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
+
+ // Filter by access group ID
+ // Required: false
+ AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,omitempty"`
+
+ // Filter by description
+ // Required: false
+ Description string `url:"description,omitempty" json:"description,omitempty"`
+
+ // Filter by create date from
+ // Required: false
+ CreatedFrom string `url:"created_from,omitempty" json:"created_from,omitempty"`
+
+ // Filter by create date to
+ // Required: false
+ CreatedTo string `url:"created_to,omitempty" json:"created_to,omitempty"`
+
+ // Filter by update date from
+ // Required: false
+ UpdatedFrom string `url:"updated_from,omitempty" json:"updated_from,omitempty"`
+
+ // Filter by update date to
+ // Required: false
+ UpdatedTo string `url:"updated_to,omitempty" json:"updated_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, subnet, created_at, updated_at)
+ // Required: false
+ SortBy string `url:"sort_by,omitempty" json:"sort_by,omitempty" validate:"omitempty,oneof=display_name subnet created_at updated_at"`
+
+ // Sort order (asc/desc)
+ // Required: false
+ SortOrder string `url:"sort_order,omitempty" json:"sort_order,omitempty" validate:"omitempty,oneof=asc desc"`
+}
+
+// List of routers
+func (i Routers) List(ctx context.Context, req ListRequest) (RoutersList, error) {
+ res, err := i.ListRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ pools := []RoutersModel{}
+
+ err = json.Unmarshal(res, &pools)
+ if err != nil {
+ return nil, err
+ }
+
+ return pools, nil
+}
+
+// ListRaw gets a list of all routers as an array of bytes
+func (a Routers) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
+
+ if err := validators.ValidateRequest(req); err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/list"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/routers/models.go b/pkg/sdn/routers/models.go
new file mode 100644
index 0000000..9e36b9a
--- /dev/null
+++ b/pkg/sdn/routers/models.go
@@ -0,0 +1,523 @@
+package routers
+
+// List of routers
+type RoutersList []RoutersModel
+
+// Main information about router
+type RoutersModel 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"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // List of gateway ports
+ GatewayPorts []GatewayPort `json:"gateway_ports"`
+
+ // ID
+ ID string `json:"id"`
+
+ // List of policies
+ Policies []Policy `json:"policies"`
+
+ // List of ports
+ Ports []Port `json:"ports"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Gateway port information
+type GatewayPort struct {
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // External L4 port maximum
+ ExternalL4PortMax int `json:"external_l4_port_max"`
+
+ // External L4 port minimum
+ ExternalL4PortMin int `json:"external_l4_port_min"`
+
+ // External network port
+ ExternalNetworkPort ExternalNetworkPort `json:"external_network_port"`
+
+ // ID
+ ID string `json:"id"`
+
+ // SNAT enabled flag
+ SNATEnabled bool `json:"snat_enabled"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// External network port information
+type ExternalNetworkPort struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Comment
+ Comment string `json:"comment"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // IPv4 address
+ IPv4 string `json:"ipv4"`
+
+ // IPv6 address
+ IPv6 string `json:"ipv6"`
+
+ // IPv6 configuration
+ 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"`
+}
+
+// IPv6 configuration information
+type IPv6Config struct {
+ // Address mode
+ AddressMode string `json:"address_mode"`
+
+ // Enable periodic RA flag
+ EnablePeriodicRA bool `json:"enable_periodic_ra"`
+
+ // Interval RA
+ IntervalRA int `json:"interval_ra"`
+
+ // Router preference
+ RouterPreference string `json:"router_preference"`
+}
+
+// Router gateway port information
+type RouterGatewayPort struct {
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Router display name
+ RouterDisplayName string `json:"router_display_name"`
+
+ // Router ID
+ RouterID string `json:"router_id"`
+
+ // SNAT enabled flag
+ SNATEnabled bool `json:"snat_enabled"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+}
+
+// Floating IP information
+type FloatingIP 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"`
+
+ // External network port
+ ExternalNetworkPort string `json:"external_network_port"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Logical port
+ LogicalPort LogicalPort `json:"logical_port"`
+
+ // Router
+ Router string `json:"router"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Logical port information
+type LogicalPort struct {
+ // ID
+ ID string `json:"id"`
+
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Adapter MAC
+ AdapterMAC string `json:"adapter_mac"`
+
+ // Address detection flag
+ AddressDetection bool `json:"address_detection"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // External network ID
+ ExternalNetworkID string `json:"external_network_id"`
+
+ // Hypervisor
+ Hypervisor string `json:"hypervisor"`
+
+ // Hypervisor display name
+ HypervisorDisplayName string `json:"hypervisor_display_name"`
+
+ // Live migration target HV
+ LiveMigrationTargetHV string `json:"live_migration_target_hv"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // Bindings information
+ Bindings Bindings `json:"bindings"`
+
+ // Unique identifier
+ UniqueIdentifier string `json:"unique_identifier"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Status information
+type Status struct {
+ // Common status
+ Common string `json:"common"`
+
+ // Hypervisors status list
+ 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"`
+
+ // Synced at time
+ SyncedAt string `json:"synced_at"`
+}
+
+// Bindings information
+type Bindings struct {
+ // ID
+ ID string `json:"id"`
+
+ // Segment display name
+ SegmentDisplayName string `json:"segment_display_name"`
+
+ // Segment ID
+ SegmentID string `json:"segment_id"`
+
+ // Port security flag
+ PortSecurity bool `json:"port_security"`
+
+ // Address detection flag
+ AddressDetection bool `json:"address_detection"`
+
+ // Is excluded from firewall flag
+ IsExcludedFromFirewall bool `json:"is_excluded_from_firewall"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Logical port addresses list
+ LogicalPortAddresses []LogicalPortAddress `json:"logical_port_addresses"`
+}
+
+// Logical port address information
+type LogicalPortAddress struct {
+ // IP address
+ IP string `json:"ip"`
+
+ // IP type
+ IPType string `json:"ip_type"`
+
+ // Is discovered flag
+ IsDiscovered bool `json:"is_discovered"`
+
+ // Is primary flag
+ IsPrimary bool `json:"is_primary"`
+
+ // MAC address
+ MAC string `json:"mac"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Logical port ID
+ LogicalPortID string `json:"logical_port_id"`
+
+ // Assigned at time
+ AssignedAt string `json:"assigned_at"`
+}
+
+// Policy information
+type Policy struct {
+ // Action
+ Action string `json:"action"`
+
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Match criteria
+ Match map[string]interface{} `json:"match"`
+
+ // Next IPv4 addresses list
+ NextIPv4Address []string `json:"next_ipv4_address"`
+
+ // Next IPv6 addresses list
+ NextIPv6Address []string `json:"next_ipv6_address"`
+
+ // Priority
+ Priority int `json:"priority"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Port information
+type Port struct {
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // IPv4 address
+ IPv4Address string `json:"ipv4_address"`
+
+ // IPv6 address
+ IPv6Address string `json:"ipv6_address"`
+
+ // IPv6 configuration
+ IPv6Config IPv6Config `json:"ipv6_config"`
+
+ // MAC address
+ MAC string `json:"mac"`
+
+ // Segment information
+ Segment Segment `json:"segment"`
+
+ // Segment ID
+ SegmentID string `json:"segment_id"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Segment information
+type Segment 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"`
+
+ // Description
+ Description string `json:"description"`
+
+ // DHCPv4 configuration
+ DHCPv4 DHCPv4 `json:"dhcp_v4"`
+
+ // DHCPv6 configuration
+ DHCPv6 DHCPv6 `json:"dhcp_v6"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Logical ports info list
+ LogicalPortsInfo []LogicalPortInfo `json:"logical_ports_info"`
+
+ // Routers info list
+ RoutersInfo []RouterInfo `json:"routers_info"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // IPv4 subnet
+ SubnetV4 string `json:"subnet_v4"`
+
+ // IPv6 subnet
+ SubnetV6 string `json:"subnet_v6"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// DHCPv4 configuration
+type DHCPv4 struct {
+ // DNS servers list
+ DNS []string `json:"dns"`
+
+ // Excluded address ranges list
+ ExcludedAddressRanges []string `json:"excluded_address_ranges"`
+
+ // Gateway address
+ Gateway string `json:"gateway"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Lease time
+ LeaseTime int `json:"lease_time"`
+
+ // Server IP
+ ServerIP string `json:"server_ip"`
+
+ // Server MAC
+ ServerMAC string `json:"server_mac"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+}
+
+// DHCPv6 configuration
+type DHCPv6 struct {
+ // Address prefix
+ AddressPrefix string `json:"address_prefix"`
+
+ // DNS servers list
+ DNS []string `json:"dns"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Lease time
+ LeaseTime int `json:"lease_time"`
+
+ // Server MAC
+ ServerMAC string `json:"server_mac"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+}
+
+// Logical port info
+type LogicalPortInfo struct {
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // ID
+ ID string `json:"id"`
+}
+
+// Router info
+type RouterInfo struct {
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // ID
+ ID string `json:"id"`
+}
diff --git a/pkg/sdn/routers/policies.go b/pkg/sdn/routers/policies.go
new file mode 100644
index 0000000..412e9f2
--- /dev/null
+++ b/pkg/sdn/routers/policies.go
@@ -0,0 +1,10 @@
+package routers
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/routers/policies"
+)
+
+// Accessing the routers policies method group
+func (r *Routers) Policies() *policies.Policies {
+ return policies.New(r.client)
+}
diff --git a/pkg/sdn/routers/policies/list.go b/pkg/sdn/routers/policies/list.go
new file mode 100644
index 0000000..ec5174e
--- /dev/null
+++ b/pkg/sdn/routers/policies/list.go
@@ -0,0 +1,66 @@
+package policies
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// ListRequest struct to get a list of policies
+type ListRequest struct {
+ // Router ID
+ // Required: true
+ RouterID string `url:"router_id" json:"router_id" validate:"required"`
+
+ // Filter by display name
+ // Required: false
+ DisplayName string `url:"display_name,omitempty" json:"display_name,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, subnet, created_at, updated_at)
+ // Required: false
+ SortBy string `url:"sort_by,omitempty" json:"sort_by,omitempty" validate:"omitempty,oneof=display_name subnet created_at updated_at"`
+
+ // Sort order (asc/desc)
+ // Required: false
+ SortOrder string `url:"sort_order,omitempty" json:"sort_order,omitempty" validate:"omitempty,oneof=asc desc"`
+}
+
+// List of policies
+func (i Policies) List(ctx context.Context, req ListRequest) (PoliciesList, error) {
+ res, err := i.ListRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ pools := []Policy{}
+
+ err = json.Unmarshal(res, &pools)
+ if err != nil {
+ return nil, err
+ }
+
+ return pools, nil
+}
+
+// ListRaw gets a list of all policies as an array of bytes
+func (a Policies) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
+
+ if err := validators.ValidateRequest(req); err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/policies/list"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/routers/policies/models.go b/pkg/sdn/routers/policies/models.go
new file mode 100644
index 0000000..2587820
--- /dev/null
+++ b/pkg/sdn/routers/policies/models.go
@@ -0,0 +1,39 @@
+package policies
+
+type PoliciesList []Policy
+
+// Policy information
+type Policy struct {
+ // Action
+ Action string `json:"action"`
+
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Match criteria
+ Match map[string]interface{} `json:"match"`
+
+ // Next IPv4 addresses list
+ NextIPv4Address []string `json:"next_ipv4_address"`
+
+ // Next IPv6 addresses list
+ NextIPv6Address []string `json:"next_ipv6_address"`
+
+ // Priority
+ Priority int `json:"priority"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
diff --git a/pkg/sdn/routers/policies/policies.go b/pkg/sdn/routers/policies/policies.go
new file mode 100644
index 0000000..5b0e862
--- /dev/null
+++ b/pkg/sdn/routers/policies/policies.go
@@ -0,0 +1,18 @@
+// API Actor API for managing SDN routers policies
+package policies
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
+)
+
+// Structure for creating request to routers policies
+type Policies struct {
+ client interfaces.Caller
+}
+
+// Builder for routers policies endpoints
+func New(client interfaces.Caller) *Policies {
+ return &Policies{
+ client,
+ }
+}
diff --git a/pkg/sdn/routers/routers.go b/pkg/sdn/routers/routers.go
new file mode 100644
index 0000000..98e94bc
--- /dev/null
+++ b/pkg/sdn/routers/routers.go
@@ -0,0 +1,18 @@
+// API Actor API for managing SDN routers
+package routers
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
+)
+
+// Structure for creating request to routers
+type Routers struct {
+ client interfaces.Caller
+}
+
+// Builder for routers endpoints
+func New(client interfaces.Caller) *Routers {
+ return &Routers{
+ client,
+ }
+}
diff --git a/pkg/sdn/routers/serialize.go b/pkg/sdn/routers/serialize.go
new file mode 100644
index 0000000..ad1e606
--- /dev/null
+++ b/pkg/sdn/routers/serialize.go
@@ -0,0 +1,27 @@
+package routers
+
+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 RoutersList) Serialize(params ...string) (serialization.Serialized, error) {
+ if len(la) == 0 {
+ return []byte{}, nil
+ }
+
+ if len(params) > 1 {
+ prefix := params[0]
+ indent := params[1]
+
+ return json.MarshalIndent(la, prefix, indent)
+ }
+
+ return json.Marshal(la)
+}
diff --git a/pkg/sdn/routers/update.go b/pkg/sdn/routers/update.go
new file mode 100644
index 0000000..e0f2ed6
--- /dev/null
+++ b/pkg/sdn/routers/update.go
@@ -0,0 +1,57 @@
+package routers
+
+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 for update router
+type UpdateRequest struct {
+ // ID of router
+ // Required: true
+ RouterID string `url:"router_id" json:"router_id" validate:"required"`
+
+ // ID of version
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" 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"`
+}
+
+// Update updated a router
+func (e Routers) Update(ctx context.Context, req UpdateRequest) (*RoutersModel, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/router/update"
+
+ res, err := e.client.DecortApiCallCtype(ctx, http.MethodPut, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := RoutersModel{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/secpolicies/create.go b/pkg/sdn/secpolicies/create.go
new file mode 100644
index 0000000..0233026
--- /dev/null
+++ b/pkg/sdn/secpolicies/create.go
@@ -0,0 +1,77 @@
+package secpolicies
+
+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 security policy
+type CreateRequest struct {
+ // Access group ID
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // Applied to net object group ID
+ // Required: true
+ AppliedToNetObjectGroupID string `url:"applied_to_net_object_group_id" json:"applied_to_net_object_group_id" validate:"required"`
+
+ // Description of the schedule rule
+ // Required: true
+ Description string `url:"description" json:"description"`
+
+ // Display name of the schedule rule
+ // Required: true
+ DisplayName string `url:"display_name" json:"display_name"`
+
+ // Enabled status of the schedule rule
+ // Required: true
+ Enabled bool `url:"enabled" json:"enabled"`
+
+ // End date and time for the schedule rule
+ // Required: false
+ EndDateTime string `url:"end_date_time,omitempty" json:"end_date_time,omitempty"`
+
+ // Insert up reference
+ // Required: false
+ InsertUp string `url:"insert_up,omitempty" json:"insert_up,omitempty"`
+
+ // Locked at timestamp
+ // Required: false
+ LockedAt string `url:"locked_at,omitempty" json:"locked_at,omitempty"`
+
+ // Schedule cron expression
+ // Required: false
+ ScheduleCron string `url:"schedule_cron,omitempty" json:"schedule_cron,omitempty"`
+
+ // Start date and time for the schedule rule
+ // Required: false
+ StartDateTime string `url:"start_date_time,omitempty" json:"start_date_time,omitempty"`
+}
+
+// Create creates a security policy
+func (i SecurityPolicies) Create(ctx context.Context, req CreateRequest) (*SecurityPolicySummary, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/security_policy/create"
+
+ res, err := i.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := SecurityPolicySummary{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/secpolicies/delete.go b/pkg/sdn/secpolicies/delete.go
new file mode 100644
index 0000000..0b18f0b
--- /dev/null
+++ b/pkg/sdn/secpolicies/delete.go
@@ -0,0 +1,41 @@
+package secpolicies
+
+import (
+ "context"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// DeleteRequest struct to delete security policy
+type DeleteRequest struct {
+ // Security policy ID
+ // Required: true
+ SecurityPolicyID string `url:"security_policy_id" json:"security_policy_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 a security policy
+func (i SecurityPolicies) Delete(ctx context.Context, req DeleteRequest) (bool, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return false, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/security_policy/delete"
+
+ _, err = i.client.DecortApiCallCtype(ctx, http.MethodDelete, url, constants.MIMEJSON, req)
+ if err != nil {
+ return false, err
+ }
+
+ return true, nil
+}
diff --git a/pkg/sdn/secpolicies/filter.go b/pkg/sdn/secpolicies/filter.go
new file mode 100644
index 0000000..28d22f9
--- /dev/null
+++ b/pkg/sdn/secpolicies/filter.go
@@ -0,0 +1,42 @@
+package secpolicies
+
+// FilterByID returns SecurityPolicyList with specified ID.
+func (agl SecurityPolicyList) FilterByID(id string) SecurityPolicyList {
+ predicate := func(ia SecurityPolicySummary) bool {
+ return ia.ID == id
+ }
+
+ return agl.FilterFunc(predicate)
+}
+
+// FilterByName returns SecurityPolicyList with specified Name.
+func (agl SecurityPolicyList) FilterByName(name string) SecurityPolicyList {
+ predicate := func(ia SecurityPolicySummary) bool {
+ return ia.DisplayName == name
+ }
+
+ return agl.FilterFunc(predicate)
+}
+
+// FilterFunc allows filtering SecurityPolicyList based on a user-specified predicate.
+func (agl SecurityPolicyList) FilterFunc(predicate func(SecurityPolicySummary) bool) SecurityPolicyList {
+ var result SecurityPolicyList
+
+ for _, acc := range agl {
+ if predicate(acc) {
+ result = append(result, acc)
+ }
+ }
+
+ return result
+}
+
+// FindOne returns first element.
+// If none was found, returns an empty struct.
+func (agl SecurityPolicyList) FindOne() SecurityPolicySummary {
+ if len(agl) == 0 {
+ return SecurityPolicySummary{}
+ }
+
+ return agl[0]
+}
diff --git a/pkg/sdn/secpolicies/filter_test.go b/pkg/sdn/secpolicies/filter_test.go
new file mode 100644
index 0000000..c4020ac
--- /dev/null
+++ b/pkg/sdn/secpolicies/filter_test.go
@@ -0,0 +1,295 @@
+package secpolicies
+
+import (
+ "testing"
+)
+
+var testSecurityPolicies = SecurityPolicyList{
+ {
+ ID: "policy1",
+ DisplayName: "DevelopersPolicy",
+ Description: "First policy",
+ CreatedAt: "2023-01-01",
+ UpdatedAt: "2023-01-10",
+ AccessGroupID: "group1",
+ AccessGroupName: "Developers",
+ AppliedToNetObjectGroupID: "netgroup1",
+ Enabled: true,
+ StartPriority: 1,
+ EndPriority: 100,
+ VersionID: 1,
+ Status: Status{
+ Common: "active",
+ Hypervisors: []HypervisorStatus{
+ {
+ Name: "hyp1",
+ DisplayName: "Hypervisor1",
+ Status: "synced",
+ HypervisorStatus: "healthy",
+ SyncedAt: "2023-01-10T10:00:00Z",
+ },
+ },
+ },
+ SecurityRules: []SecurityRule{
+ {
+ ID: "rule1",
+ DisplayName: "AllowHTTP",
+ Description: "Allow HTTP traffic",
+ Action: "Allow",
+ Direction: "Ingress",
+ Enabled: true,
+ Priority: 10,
+ SecurityPolicyID: "policy1",
+ LogEnabled: true,
+ LogSeverity: "medium",
+ StatisticsEnabled: true,
+ VersionID: 1,
+ Filter: Filter{
+ Filters: map[string]interface{}{
+ "protocol": "tcp",
+ "port": float64(80),
+ },
+ },
+ },
+ },
+ },
+ {
+ ID: "policy2",
+ DisplayName: "AdminsPolicy",
+ Description: "Second policy",
+ CreatedAt: "2023-01-02",
+ UpdatedAt: "2023-01-11",
+ AccessGroupID: "group2",
+ AccessGroupName: "Admins",
+ AppliedToNetObjectGroupID: "netgroup2",
+ Enabled: false,
+ StartPriority: 101,
+ EndPriority: 200,
+ VersionID: 2,
+ Status: Status{
+ Common: "inactive",
+ Hypervisors: []HypervisorStatus{
+ {
+ Name: "hyp2",
+ DisplayName: "Hypervisor2",
+ Status: "pending",
+ HypervisorStatus: "syncing",
+ SyncedAt: "2023-01-11T10:00:00Z",
+ },
+ },
+ },
+ SecurityRules: []SecurityRule{
+ {
+ ID: "rule2",
+ DisplayName: "DenySSH",
+ Description: "Deny SSH traffic",
+ Action: "Deny",
+ Direction: "Ingress",
+ Enabled: true,
+ Priority: 20,
+ SecurityPolicyID: "policy2",
+ LogEnabled: false,
+ LogSeverity: "high",
+ StatisticsEnabled: false,
+ VersionID: 1,
+ Filter: Filter{
+ Filters: map[string]interface{}{
+ "protocol": "tcp",
+ "port": float64(22),
+ },
+ },
+ },
+ },
+ },
+ {
+ ID: "policy3",
+ DisplayName: "UsersPolicy",
+ Description: "Third policy",
+ CreatedAt: "2023-01-03",
+ UpdatedAt: "2023-01-12",
+ AccessGroupID: "group3",
+ AccessGroupName: "Users",
+ AppliedToNetObjectGroupID: "netgroup3",
+ Enabled: true,
+ StartPriority: 201,
+ EndPriority: 300,
+ VersionID: 3,
+ Status: Status{
+ Common: "active",
+ Hypervisors: []HypervisorStatus{
+ {
+ Name: "hyp3",
+ DisplayName: "Hypervisor3",
+ Status: "synced",
+ HypervisorStatus: "healthy",
+ SyncedAt: "2023-01-12T10:00:00Z",
+ },
+ },
+ },
+ SecurityRules: []SecurityRule{
+ {
+ ID: "rule3",
+ DisplayName: "AllowHTTPS",
+ Description: "Allow HTTPS traffic",
+ Action: "Allow",
+ Direction: "Egress",
+ Enabled: true,
+ Priority: 30,
+ SecurityPolicyID: "policy3",
+ LogEnabled: true,
+ LogSeverity: "low",
+ StatisticsEnabled: true,
+ VersionID: 1,
+ Filter: Filter{
+ Filters: map[string]interface{}{
+ "protocol": "tcp",
+ "port": float64(443),
+ },
+ },
+ },
+ },
+ },
+}
+
+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 SecurityPolicySummary) bool {
+ return sp.Description == "Second policy"
+ })
+
+ if len(actual) != 1 || actual[0].ID != "policy2" {
+ t.Fatal("Expected 1 policy with description 'Second policy', found:", len(actual))
+ }
+}
+
+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 := SecurityPolicyList{}
+ result := emptyList.FindOne()
+
+ if result.ID != "" || result.DisplayName != "" {
+ t.Fatal("Expected empty SecurityPolicySummary, got:", result)
+ }
+}
+
+func TestFilterByIDNotFound(t *testing.T) {
+ actual := testSecurityPolicies.FilterByID("nonexistent")
+
+ if len(actual) != 0 {
+ t.Fatal("Expected 0 policies, found:", len(actual))
+ }
+}
+
+func TestFilterByDisplayNameNotFound(t *testing.T) {
+ actual := testSecurityPolicies.FilterByName("Nonexistent Policy")
+
+ if len(actual) != 0 {
+ t.Fatal("Expected 0 policies, found:", len(actual))
+ }
+}
+
+func TestFilterByEnabled(t *testing.T) {
+ actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicySummary) bool {
+ return sp.Enabled
+ })
+
+ if len(actual) != 2 {
+ t.Fatal("Expected 2 enabled policies, found:", len(actual))
+ }
+}
+
+func TestFilterByAccessGroup(t *testing.T) {
+ actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicySummary) bool {
+ return sp.AccessGroupName == "Developers"
+ })
+
+ if len(actual) != 1 || actual[0].ID != "policy1" {
+ t.Fatal("Expected 1 policy for Developers group, found:", len(actual))
+ }
+}
+
+func TestFilterByStatus(t *testing.T) {
+ actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicySummary) bool {
+ return sp.Status.Common == "active"
+ })
+
+ if len(actual) != 2 {
+ t.Fatal("Expected 2 active policies, found:", len(actual))
+ }
+}
+
+func TestFilterByPriorityRange(t *testing.T) {
+ actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicySummary) bool {
+ return sp.StartPriority >= 100 && sp.EndPriority <= 200
+ })
+
+ if len(actual) != 1 || actual[0].ID != "policy2" {
+ t.Fatal("Expected 1 policy in priority range 100-200, found:", len(actual))
+ }
+}
+
+func TestFilterByRuleAction(t *testing.T) {
+ actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicySummary) bool {
+ for _, rule := range sp.SecurityRules {
+ if rule.Action == "Deny" {
+ return true
+ }
+ }
+ return false
+ })
+
+ if len(actual) != 1 || actual[0].ID != "policy2" {
+ t.Fatal("Expected 1 policy with Deny rule, found:", len(actual))
+ }
+}
+
+func TestFilterByRuleDirection(t *testing.T) {
+ actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicySummary) bool {
+ for _, rule := range sp.SecurityRules {
+ if rule.Direction == "Egress" {
+ return true
+ }
+ }
+ return false
+ })
+
+ if len(actual) != 1 || actual[0].ID != "policy3" {
+ t.Fatal("Expected 1 policy with Egress rule, found:", len(actual))
+ }
+}
+
+func TestFilterByLogEnabled(t *testing.T) {
+ actual := testSecurityPolicies.FilterFunc(func(sp SecurityPolicySummary) bool {
+ for _, rule := range sp.SecurityRules {
+ if rule.LogEnabled {
+ return true
+ }
+ }
+ return false
+ })
+
+ if len(actual) != 2 {
+ t.Fatal("Expected 2 policies with log enabled rules, found:", len(actual))
+ }
+}
diff --git a/pkg/sdn/secpolicies/get.go b/pkg/sdn/secpolicies/get.go
new file mode 100644
index 0000000..d7dae83
--- /dev/null
+++ b/pkg/sdn/secpolicies/get.go
@@ -0,0 +1,47 @@
+package secpolicies
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// GetRequest struct to get information about security policy
+type GetRequest struct {
+ // ID a security policy
+ // Required: true
+ ID string `url:"security_policy_id" json:"security_policy_id" validate:"required"`
+}
+
+// Get gets security policy
+func (a SecurityPolicies) Get(ctx context.Context, req GetRequest) (*SecurityPolicySummary, error) {
+ res, err := a.GetRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := SecurityPolicySummary{}
+
+ 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 SecurityPolicies) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/security_policies/get"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/secpolicies/ids.go b/pkg/sdn/secpolicies/ids.go
new file mode 100644
index 0000000..3974611
--- /dev/null
+++ b/pkg/sdn/secpolicies/ids.go
@@ -0,0 +1,10 @@
+package secpolicies
+
+// IDs gets array of IDs from SecurityPolicyList struct
+func (spl SecurityPolicyList) IDs() []string {
+ res := make([]string, 0, len(spl))
+ for _, c := range spl {
+ res = append(res, c.ID)
+ }
+ return res
+}
diff --git a/pkg/sdn/secpolicies/list.go b/pkg/sdn/secpolicies/list.go
new file mode 100644
index 0000000..b339a8f
--- /dev/null
+++ b/pkg/sdn/secpolicies/list.go
@@ -0,0 +1,74 @@
+package secpolicies
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// ListRequest struct to get a list of security group
+type ListRequest struct {
+ // Display name
+ // Required: false
+ DisplayName string `url:"display_name,omitempty" json:"display_name,omitempty"`
+
+ // Enabled status
+ // Required: false
+ Enabled interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
+
+ // Filter by access group ID
+ // Required: false
+ AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,omitempty"`
+
+ // Filter by applied to net object group ID
+ // Required: false
+ AppliedToNetObjectGroupID string `url:"applied_to_net_object_group_id,omitempty" json:"applied_to_net_object_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 (display_name, enabled, created_at, updated_at, deleted_at, start_priority)
+ // 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 security policies
+func (i SecurityPolicies) List(ctx context.Context, req ListRequest) (SecurityPolicyList, error) {
+ res, err := i.ListRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ result := []SecurityPolicySummary{}
+
+ err = json.Unmarshal(res, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ return result, nil
+}
+
+// ListRaw gets a list of all security policies as an array of bytes
+func (a SecurityPolicies) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
+
+ if err := validators.ValidateRequest(req); err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/security_policy/list"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/secpolicies/models.go b/pkg/sdn/secpolicies/models.go
new file mode 100644
index 0000000..f746425
--- /dev/null
+++ b/pkg/sdn/secpolicies/models.go
@@ -0,0 +1,126 @@
+package secpolicies
+
+type SecurityPolicyList []SecurityPolicySummary
+
+// SecurityPolicySummary provides brief information about the security policy
+type SecurityPolicySummary struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Access group name
+ AccessGroupName string `json:"access_group_name"`
+
+ // Applied to network object group ID
+ AppliedToNetObjectGroupID string `json:"applied_to_net_object_group_id"`
+
+ // Created time
+ CreatedAt string `json:"created_at"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // End priority
+ EndPriority int `json:"end_priority"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Security rules
+ SecurityRules []SecurityRule `json:"security_rules"`
+
+ // Start priority
+ StartPriority int `json:"start_priority"`
+
+ // Status information
+ Status Status `json:"status"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+
+ // Updated time
+ UpdatedAt string `json:"updated_at"`
+}
+
+// Status information
+type Status struct {
+ // Common status
+ Common string `json:"common"`
+
+ // Hypervisor statuses
+ Hypervisors []HypervisorStatus `json:"hypervisors"`
+}
+
+// HypervisorStatus 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"`
+}
+
+// Security rules
+type SecurityRule struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Action to take (Allow, Deny, etc.)
+ Action string `json:"action"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Traffic direction (Ingress, Egress)
+ Direction string `json:"direction"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // Filter criteria
+ Filter Filter `json:"filter"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Log enabled flag
+ LogEnabled bool `json:"log_enabled"`
+
+ // Log severity level
+ LogSeverity string `json:"log_severity"`
+
+ // Priority
+ Priority int `json:"priority"`
+
+ // Security policy ID
+ SecurityPolicyID string `json:"security_policy_id"`
+
+ // Statistics enabled flag
+ StatisticsEnabled bool `json:"statistics_enabled"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Filter represents the filter criteria for the security rule
+type Filter struct {
+ // Filters map
+ Filters map[string]interface{} `json:"filters"`
+}
diff --git a/pkg/sdn/secpolicies/move.go b/pkg/sdn/secpolicies/move.go
new file mode 100644
index 0000000..d1ca872
--- /dev/null
+++ b/pkg/sdn/secpolicies/move.go
@@ -0,0 +1,49 @@
+package secpolicies
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/constants"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// DeleteRequest struct to move security policy
+type MoveRequest struct {
+ // Security policy ID
+ // Required: true
+ SecurityPolicyID string `url:"security_policy_id" json:"security_policy_id" validate:"required"`
+
+ // Version ID
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // Security policy ID
+ // Required: true
+ InsertUp string `url:"insert_up" json:"insert_up" validate:"required"`
+}
+
+// Move a security policy
+func (i SecurityPolicies) Move(ctx context.Context, req MoveRequest) (*SecurityPolicySummary, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/security_policy/move"
+
+ res, err := i.client.DecortApiCallCtype(ctx, http.MethodPatch, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := SecurityPolicySummary{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/secpolicies/rule.go b/pkg/sdn/secpolicies/rule.go
new file mode 100644
index 0000000..f33d6e7
--- /dev/null
+++ b/pkg/sdn/secpolicies/rule.go
@@ -0,0 +1,10 @@
+package secpolicies
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/secpolicies/rule"
+)
+
+// Accessing the security policies rule method group
+func (r *SecurityPolicies) Rule() *rule.Rule {
+ return rule.New(r.client)
+}
diff --git a/pkg/sdn/secpolicies/rule/get.go b/pkg/sdn/secpolicies/rule/get.go
new file mode 100644
index 0000000..d799d93
--- /dev/null
+++ b/pkg/sdn/secpolicies/rule/get.go
@@ -0,0 +1,50 @@
+package rule
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// GetRequest struct to get a security rules
+type GetRequest struct {
+ // Security policy ID
+ // Required: true
+ SecurityPolicyID string `url:"security_policy_id" json:"security_policy_id" validate:"required"`
+
+ // Security rule ID
+ // Required: true
+ SecurityRuleID string `url:"security_rule_id" json:"security_rule_id" validate:"required"`
+}
+
+// Get a security policies
+func (i Rule) Get(ctx context.Context, req GetRequest) (*SecurityRule, error) {
+ res, err := i.GetRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ result := SecurityRule{}
+
+ err = json.Unmarshal(res, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ return &result, nil
+}
+
+// GetRaw gets a security rule as an array of bytes
+func (a Rule) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
+
+ if err := validators.ValidateRequest(req); err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/security_policy/rule/get"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/secpolicies/rule/ids.go b/pkg/sdn/secpolicies/rule/ids.go
new file mode 100644
index 0000000..2f03559
--- /dev/null
+++ b/pkg/sdn/secpolicies/rule/ids.go
@@ -0,0 +1,10 @@
+package rule
+
+// IDs gets array of IDs from SecurityRulesList struct
+func (srl SecurityRulesList) IDs() []string {
+ res := make([]string, 0, len(srl))
+ for _, c := range srl {
+ res = append(res, c.ID)
+ }
+ return res
+}
diff --git a/pkg/sdn/secpolicies/rule/list.go b/pkg/sdn/secpolicies/rule/list.go
new file mode 100644
index 0000000..d994dff
--- /dev/null
+++ b/pkg/sdn/secpolicies/rule/list.go
@@ -0,0 +1,70 @@
+package rule
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// ListRequest struct to get a list of security rules
+type ListRequest struct {
+ // Security policy ID
+ // Required: true
+ SecurityPolicyID string `url:"security_policy_id" json:"security_policy_id" validate:"required"`
+
+ // Display name
+ // Required: false
+ DisplayName string `url:"display_name,omitempty" json:"display_name,omitempty"`
+
+ // Enabled status
+ // Required: false
+ Enabled interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
+
+ // 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, enabled, created_at, updated_at, deleted_at, start_priority)
+ // 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 security policies
+func (i Rule) List(ctx context.Context, req ListRequest) (SecurityRulesList, error) {
+ res, err := i.ListRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ result := []SecurityRule{}
+
+ err = json.Unmarshal(res, &result)
+ if err != nil {
+ return nil, err
+ }
+
+ return result, nil
+}
+
+// ListRaw gets a list of all security rules as an array of bytes
+func (a Rule) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
+
+ if err := validators.ValidateRequest(req); err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/security_policy/rule/list"
+
+ res, err := a.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/secpolicies/rule/models.go b/pkg/sdn/secpolicies/rule/models.go
new file mode 100644
index 0000000..4a73bc4
--- /dev/null
+++ b/pkg/sdn/secpolicies/rule/models.go
@@ -0,0 +1,54 @@
+package rule
+
+type SecurityRulesList []SecurityRule
+
+// SecurityRule
+type SecurityRule struct {
+ // Access group ID
+ AccessGroupID string `json:"access_group_id"`
+
+ // Action to take (Allow, Deny, etc.)
+ Action string `json:"action"`
+
+ // Description
+ Description string `json:"description"`
+
+ // Traffic direction (Ingress, Egress)
+ Direction string `json:"direction"`
+
+ // Display name
+ DisplayName string `json:"display_name"`
+
+ // Enabled flag
+ Enabled bool `json:"enabled"`
+
+ // Filter criteria
+ Filter Filter `json:"filter"`
+
+ // ID
+ ID string `json:"id"`
+
+ // Log enabled flag
+ LogEnabled bool `json:"log_enabled"`
+
+ // Log severity level
+ LogSeverity string `json:"log_severity"`
+
+ // Priority
+ Priority int `json:"priority"`
+
+ // Security policy ID
+ SecurityPolicyID string `json:"security_policy_id"`
+
+ // Statistics enabled flag
+ StatisticsEnabled bool `json:"statistics_enabled"`
+
+ // Version ID
+ VersionID uint64 `json:"version_id"`
+}
+
+// Filter represents the filter criteria for the security rule
+type Filter struct {
+ // Filters map
+ Filters map[string]interface{} `json:"filters"`
+}
diff --git a/pkg/sdn/secpolicies/rule/rule.go b/pkg/sdn/secpolicies/rule/rule.go
new file mode 100644
index 0000000..393322a
--- /dev/null
+++ b/pkg/sdn/secpolicies/rule/rule.go
@@ -0,0 +1,18 @@
+// API Actor API for managing SDN security policies rule
+package rule
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
+)
+
+// Structure for creating request to security policies rule
+type Rule struct {
+ client interfaces.Caller
+}
+
+// Builder for security policies rule endpoints
+func New(client interfaces.Caller) *Rule {
+ return &Rule{
+ client,
+ }
+}
diff --git a/pkg/sdn/secpolicies/rule/serialize.go b/pkg/sdn/secpolicies/rule/serialize.go
new file mode 100644
index 0000000..577f285
--- /dev/null
+++ b/pkg/sdn/secpolicies/rule/serialize.go
@@ -0,0 +1,27 @@
+package rule
+
+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 SecurityRulesList) Serialize(params ...string) (serialization.Serialized, error) {
+ if len(la) == 0 {
+ return []byte{}, nil
+ }
+
+ if len(params) > 1 {
+ prefix := params[0]
+ indent := params[1]
+
+ return json.MarshalIndent(la, prefix, indent)
+ }
+
+ return json.Marshal(la)
+}
diff --git a/pkg/sdn/secpolicies/security_policies.go b/pkg/sdn/secpolicies/security_policies.go
new file mode 100644
index 0000000..9fe1028
--- /dev/null
+++ b/pkg/sdn/secpolicies/security_policies.go
@@ -0,0 +1,18 @@
+// API Actor API for managing SDN secirity policies
+package secpolicies
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
+)
+
+// Structure for creating request to security policies
+type SecurityPolicies struct {
+ client interfaces.Caller
+}
+
+// Builder for adress pools endpoints
+func New(client interfaces.Caller) *SecurityPolicies {
+ return &SecurityPolicies{
+ client,
+ }
+}
diff --git a/pkg/sdn/secpolicies/serialize.go b/pkg/sdn/secpolicies/serialize.go
new file mode 100644
index 0000000..1dc7ee2
--- /dev/null
+++ b/pkg/sdn/secpolicies/serialize.go
@@ -0,0 +1,27 @@
+package secpolicies
+
+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 SecurityPolicyList) Serialize(params ...string) (serialization.Serialized, error) {
+ if len(la) == 0 {
+ return []byte{}, nil
+ }
+
+ if len(params) > 1 {
+ prefix := params[0]
+ indent := params[1]
+
+ return json.MarshalIndent(la, prefix, indent)
+ }
+
+ return json.Marshal(la)
+}
diff --git a/pkg/sdn/secpolicies/update.go b/pkg/sdn/secpolicies/update.go
new file mode 100644
index 0000000..ddcd0d0
--- /dev/null
+++ b/pkg/sdn/secpolicies/update.go
@@ -0,0 +1,80 @@
+package secpolicies
+
+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 security policy
+type UpdateRequest struct {
+ // ID a security policy
+ // Required: true
+ SecurityPolicyID string `url:"security_policy_id" json:"security_policy_id" validate:"required"`
+
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // Applied to net object group ID
+ // Required: true
+ AppliedToNetObjectGroupID string `url:"applied_to_net_object_group_id" json:"applied_to_net_object_group_id" validate:"required"`
+
+ // Description of the schedule rule
+ // Required: true
+ Description string `url:"description" json:"description"`
+
+ // Display name of the schedule rule
+ // Required: true
+ DisplayName string `url:"display_name" json:"display_name"`
+
+ // Enabled status of the schedule rule
+ // Required: true
+ Enabled bool `url:"enabled" json:"enabled"`
+
+ // End date and time for the schedule rule
+ // Required: false
+ EndDateTime string `url:"end_date_time,omitempty" json:"end_date_time,omitempty"`
+
+ // Insert up reference
+ // Required: false
+ InsertUp string `url:"insert_up,omitempty" json:"insert_up,omitempty"`
+
+ // Locked at timestamp
+ // Required: false
+ LockedAt string `url:"locked_at,omitempty" json:"locked_at,omitempty"`
+
+ // Schedule cron expression
+ // Required: false
+ ScheduleCron string `url:"schedule_cron,omitempty" json:"schedule_cron,omitempty"`
+
+ // Start date and time for the schedule rule
+ // Required: false
+ StartDateTime string `url:"start_date_time,omitempty" json:"start_date_time,omitempty"`
+}
+
+// Update updates a security policy
+func (i SecurityPolicies) Update(ctx context.Context, req UpdateRequest) (*SecurityPolicySummary, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/security_policy/update"
+
+ res, err := i.client.DecortApiCallCtype(ctx, http.MethodPut, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := SecurityPolicySummary{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/security_policies.go b/pkg/sdn/security_policies.go
new file mode 100644
index 0000000..63a463e
--- /dev/null
+++ b/pkg/sdn/security_policies.go
@@ -0,0 +1,10 @@
+package sdn
+
+import (
+ sp "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/secpolicies"
+)
+
+// Accessing the SDN method group
+func (sdn *SDN) SecurityPolicies() *sp.SecurityPolicies {
+ return sp.New(sdn.client)
+}
diff --git a/pkg/sdn/segments.go b/pkg/sdn/segments.go
new file mode 100644
index 0000000..7f84bc7
--- /dev/null
+++ b/pkg/sdn/segments.go
@@ -0,0 +1,10 @@
+package sdn
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/segments"
+)
+
+// Accessing the Segments method group
+func (sdn *SDN) Segments() *segments.Segments {
+ return segments.New(sdn.client)
+}
diff --git a/pkg/sdn/segments/create.go b/pkg/sdn/segments/create.go
new file mode 100644
index 0000000..f71fa8b
--- /dev/null
+++ b/pkg/sdn/segments/create.go
@@ -0,0 +1,121 @@
+package segments
+
+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 for creating segment
+type CreateRequest struct {
+ // Identifier of the parent access group
+ // Required: false
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // Detailed description of the segment
+ // Required: true
+ Description string `url:"description" json:"description" validate:"required"`
+
+ // User-friendly name for the segment
+ // 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 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"`
+
+ // DHCP IPv4
+ // Required: false
+ DHCPv4 *DHCPv4ConfigRequest `url:"-" json:"dhcp_v4,omitempty"`
+
+ // DHCP IPv6
+ // Required: false
+ DHCPv6 *DHCPv6ConfigRequest `url:"-" json:"dhcp_v6,omitempty"`
+}
+
+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"`
+
+ // Lease time
+ // Required: false
+ LeaseTime uint64 `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"`
+
+ // Lease time
+ // Required: true
+ LeaseTime uint64 `url:"lease_time,omitempty" json:"lease_time,omitempty"`
+
+ // Server MAC
+ // Required: true
+ ServerMAC string `url:"server_mac,omitempty" json:"server_mac,omitempty"`
+
+ // Whether the config is enabled
+ // Required: true
+ Enabled bool `url:"enabled" json:"enabled"`
+}
+
+// Create creates segment
+func (s Segments) Create(ctx context.Context, req CreateRequest) (*SegmentResponse, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/segment/create"
+
+ res, err := s.client.DecortApiCallCtype(ctx, http.MethodPost, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := SegmentResponse{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/pkg/sdn/segments/delete.go b/pkg/sdn/segments/delete.go
new file mode 100644
index 0000000..2623233
--- /dev/null
+++ b/pkg/sdn/segments/delete.go
@@ -0,0 +1,42 @@
+package segments
+
+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 segment
+type DeleteRequest struct {
+ // ID of segment
+ // Required: true
+ SegmentID string `url:"segment_id" json:"segment_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 segment
+func (s Segments) Delete(ctx context.Context, req DeleteRequest) error {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/segment/delete"
+
+ _, err = s.client.DecortApiCallCtype(ctx, http.MethodDelete, url, constants.MIMEJSON, req)
+
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
diff --git a/pkg/sdn/segments/filter.go b/pkg/sdn/segments/filter.go
new file mode 100644
index 0000000..efe8fa7
--- /dev/null
+++ b/pkg/sdn/segments/filter.go
@@ -0,0 +1,60 @@
+package segments
+
+// FilterByID returns ListSegment with specified ID.
+func (list ListSegment) FilterByID(id string) ListSegment {
+ predicate := func(segment SegmentResponse) bool {
+ return segment.ID == id
+ }
+
+ return list.FilterFunc(predicate)
+}
+
+// FilterByName returns ListSegment with specified display name.
+func (list ListSegment) FilterByName(name string) ListSegment {
+ predicate := func(segment SegmentResponse) bool {
+ return segment.DisplayName == name
+ }
+
+ return list.FilterFunc(predicate)
+}
+
+// FilterBySubnetIPv4 returns ListSegment with specified subnet IPv4.
+func (list ListSegment) FilterBySubnetIPv4(IPv4 string) ListSegment {
+ predicate := func(segment SegmentResponse) bool {
+ return segment.SubnetV4 == IPv4
+ }
+
+ return list.FilterFunc(predicate)
+}
+
+// FilterBySubnetIPv6 returns ListSegment with specified subnet IPv4.
+func (list ListSegment) FilterBySubnetIPv6(IPv6 string) ListSegment {
+ predicate := func(segment SegmentResponse) bool {
+ return segment.SubnetV6 == IPv6
+ }
+
+ return list.FilterFunc(predicate)
+}
+
+// FilterFunc allows filtering ListSegment based on a user-specified predicate.
+func (list ListSegment) FilterFunc(predicate func(response SegmentResponse) bool) ListSegment {
+ var result ListSegment
+
+ for _, item := range list {
+ if predicate(item) {
+ result = append(result, item)
+ }
+ }
+
+ return result
+}
+
+// FindOne returns first element.
+// If none was found, returns an empty struct.
+func (list ListSegment) FindOne() SegmentResponse {
+ if len(list) == 0 {
+ return SegmentResponse{}
+ }
+
+ return list[0]
+}
diff --git a/pkg/sdn/segments/filter_test.go b/pkg/sdn/segments/filter_test.go
new file mode 100644
index 0000000..517c09e
--- /dev/null
+++ b/pkg/sdn/segments/filter_test.go
@@ -0,0 +1,153 @@
+package segments
+
+import (
+ "testing"
+ "time"
+)
+
+var testSegmentList = ListSegment{
+ {
+ AccessGroupID: "a1b2c3d4-1234-5678-9101-abcdef123456",
+ AccessGroupName: "default-access-group",
+ CreatedAt: time.Date(2024, 1, 15, 10, 30, 0, 0, time.UTC),
+ Description: "Test1",
+ DHCPv4: DHCPv4Config{},
+ DHCPv6: DHCPv6Config{},
+ DisplayName: "Test1",
+ Enabled: true,
+ ID: "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
+ LogicalPortsInfo: []EntityInfo{},
+ RoutersInfo: []EntityInfo{},
+ Status: Status{},
+ SubnetV4: "192.168.1.0/24",
+ SubnetV6: "2001:db8::/64",
+ UpdatedAt: time.Date(2024, 1, 20, 14, 45, 0, 0, time.UTC),
+ VersionID: 1,
+ },
+ {
+ AccessGroupID: "b2c3d4e5-2345-6789-0123-bcdefa654321",
+ AccessGroupName: "restricted-group",
+ CreatedAt: time.Date(2024, 2, 1, 9, 0, 0, 0, time.UTC),
+ Description: "Test2",
+ DHCPv4: DHCPv4Config{},
+ DHCPv6: DHCPv6Config{},
+ DisplayName: "Test2",
+ Enabled: false,
+ ID: "c3d4e5f6-3456-7890-1234-cdefab098765",
+ LogicalPortsInfo: []EntityInfo{},
+ RoutersInfo: []EntityInfo{},
+ Status: Status{},
+ SubnetV4: "10.0.0.0/24",
+ SubnetV6: "2001:db8:1::/64",
+ UpdatedAt: time.Date(2024, 2, 5, 16, 20, 0, 0, time.UTC),
+ VersionID: 2,
+ },
+ {
+ AccessGroupID: "d4e5f6a7-4567-8901-2345-defabc123456",
+ AccessGroupName: "minimal-group",
+ CreatedAt: time.Date(2024, 3, 10, 12, 0, 0, 0, time.UTC),
+ Description: "Test3",
+ DHCPv4: DHCPv4Config{},
+ DHCPv6: DHCPv6Config{},
+ DisplayName: "Test3",
+ Enabled: true,
+ ID: "e5f6a7b8-5678-9012-3456-efabcd789012",
+ LogicalPortsInfo: []EntityInfo{},
+ RoutersInfo: []EntityInfo{},
+ Status: Status{},
+ SubnetV4: "192.15.1.0/24",
+ SubnetV6: "",
+ UpdatedAt: time.Date(2024, 3, 10, 12, 0, 0, 0, time.UTC),
+ VersionID: 1,
+ },
+}
+
+func TestFilterByID(t *testing.T) {
+ actual := testSegmentList.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 := testSegmentList.FilterByName("Test1").FindOne()
+
+ if actual.DisplayName != "Test1" {
+ t.Fatal("actual:", actual.DisplayName, ">> expected: Test1")
+ }
+}
+
+func TestFilterBySubnetIPv4(t *testing.T) {
+ actual := testSegmentList.FilterBySubnetIPv4("192.168.1.0/24").FindOne()
+
+ if actual.SubnetV4 != "192.168.1.0/24" {
+ t.Fatal("actual:", actual.SubnetV4, ">> expected: 192.168.1.0/24")
+ }
+}
+
+func TestFilterBySubnetIPv6(t *testing.T) {
+ actual := testSegmentList.FilterBySubnetIPv6("2001:db8::/64").FindOne()
+
+ if actual.SubnetV6 != "2001:db8::/64" {
+ t.Fatal("actual:", actual.SubnetV6, ">> expected: 2001:db8::/64")
+ }
+}
+
+func TestFilterFunc(t *testing.T) {
+ actual := testSegmentList.FilterFunc(func(response SegmentResponse) bool {
+ return response.DisplayName == "Test2"
+ })
+
+ if len(actual) != 1 || actual[0].ID != "c3d4e5f6-3456-7890-1234-cdefab098765" {
+ t.Fatal("Expected 1 extnet with name 'Test2', found:", len(actual))
+ }
+}
+
+func TestFindOneWithResults(t *testing.T) {
+ result := testSegmentList.FilterByID("c3d4e5f6-3456-7890-1234-cdefab098765").FindOne()
+ if result.ID != "c3d4e5f6-3456-7890-1234-cdefab098765" {
+ t.Fatal("Expected c3d4e5f6-3456-7890-1234-cdefab098765, got:", result.ID)
+ }
+}
+
+func TestFindOneEmpty(t *testing.T) {
+ emptyList := ListSegment{}
+ result := emptyList.FindOne()
+
+ if result.ID != "" || result.DisplayName != "" {
+ t.Fatal("Expected empty segment, got:", result)
+ }
+}
+
+func TestFilterByIDNotFound(t *testing.T) {
+ actual := ListSegment{}.FilterByID("nonexistent")
+
+ if len(actual) != 0 {
+ t.Fatal("Expected 0 extNet, found:", len(actual))
+ }
+}
+
+func TestFilterByNameNotFound(t *testing.T) {
+ actual := ListSegment{}.FilterByName("Nonexistent")
+
+ if len(actual) != 0 {
+ t.Fatal("Expected 0 extNet, found:", len(actual))
+ }
+}
+
+func TestFilterBySegmentIPv4NotFound(t *testing.T) {
+ actual := testSegmentList.FilterBySubnetIPv4("nonexistent-ip")
+
+ if len(actual) != 0 {
+ t.Fatal("Expected 0 extNet, found:", len(actual))
+ }
+}
+
+func TestFilterBySubnetIPv6NotFound(t *testing.T) {
+ actual := testSegmentList.FilterBySubnetIPv6("nonexistent-ipv6")
+
+ if len(actual) != 0 {
+ t.Fatal("Expected 0 extNet, found:", len(actual))
+ }
+}
diff --git a/pkg/sdn/segments/get.go b/pkg/sdn/segments/get.go
new file mode 100644
index 0000000..215f3af
--- /dev/null
+++ b/pkg/sdn/segments/get.go
@@ -0,0 +1,48 @@
+package segments
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+)
+
+// GetRequest struct to get information about segment
+type GetRequest struct {
+ // ID of segment
+ // Required: true
+ SegmentID string `url:"segment_id" json:"segment_id" validate:"required"`
+
+ // ID of access group
+ // Required: false
+ AccessGroupID string `url:"access_group_id,omitempty" json:"access_group_id,omitempty"`
+}
+
+// Get gets segment details as a SegmentResponse struct
+func (s Segments) Get(ctx context.Context, req GetRequest) (*SegmentResponse, error) {
+ res, err := s.GetRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := SegmentResponse{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
+
+// GetRaw gets segment details as an array of bytes
+func (s Segments) GetRaw(ctx context.Context, req GetRequest) ([]byte, error) {
+
+ //if err := validators.ValidateRequest(req); err != nil {
+ // return nil, validators.ValidationErrors(validators.GetErrors(err))
+ //}
+
+ url := "/sdn/segment/get"
+
+ res, err := s.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/segments/get_status.go b/pkg/sdn/segments/get_status.go
new file mode 100644
index 0000000..07e3774
--- /dev/null
+++ b/pkg/sdn/segments/get_status.go
@@ -0,0 +1,51 @@
+package segments
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// GetStatusRequest struct to get information about segment status
+type GetStatusRequest struct {
+ // ID of segment
+ // Required: true
+ SegmentID string `url:"segment_id" json:"segment_id" validate:"required"`
+
+ // ID of version
+ // Required: false
+ VersionID uint64 `url:"version_id,omitempty" json:"version_id,omitempty"`
+
+ // Get detailed status or not
+ // Required: false
+ Detailed interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
+}
+
+// GetStatus gets segment status
+func (s Segments) GetStatus(ctx context.Context, req GetStatusRequest) (string, error) {
+ type temp struct {
+ Status string `json:"status"`
+ }
+
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return "", validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/segment/get_status"
+
+ res, err := s.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ if err != nil {
+ return "", err
+ }
+
+ info := temp{}
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return "", err
+ }
+
+ return info.Status, nil
+}
diff --git a/pkg/sdn/segments/ids.go b/pkg/sdn/segments/ids.go
new file mode 100644
index 0000000..25490c8
--- /dev/null
+++ b/pkg/sdn/segments/ids.go
@@ -0,0 +1,10 @@
+package segments
+
+// IDs gets array of IDs from ListSegment struct
+func (list ListSegment) IDs() []string {
+ res := make([]string, 0, len(list))
+ for _, item := range list {
+ res = append(res, item.ID)
+ }
+ return res
+}
diff --git a/pkg/sdn/segments/list.go b/pkg/sdn/segments/list.go
new file mode 100644
index 0000000..6375094
--- /dev/null
+++ b/pkg/sdn/segments/list.go
@@ -0,0 +1,92 @@
+package segments
+
+import (
+ "context"
+ "encoding/json"
+ "net/http"
+)
+
+// ListRequest struct to get a list of segments
+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 enabled status
+ // Required: false
+ Enabled interface{} `url:"enabled,omitempty" json:"enabled,omitempty" validate:"omitempty,isBool"`
+
+ // Does Core currently believe that its data is synchronized with the data in the OVN?
+ // Required: false
+ IsSynced interface{} `url:"is_synced,omitempty" json:"is_synced,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, subnet)
+ // 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 segments as a ListSegment struct
+func (s Segments) List(ctx context.Context, req ListRequest) (ListSegment, error) {
+ res, err := s.ListRaw(ctx, req)
+ if err != nil {
+ return nil, err
+ }
+
+ list := ListSegment{}
+
+ err = json.Unmarshal(res, &list)
+ if err != nil {
+ return nil, err
+ }
+
+ return list, nil
+}
+
+// ListRaw gets list of all available segments as an array of bytes
+func (s Segments) ListRaw(ctx context.Context, req ListRequest) ([]byte, error) {
+
+ //if err := validators.ValidateRequest(req); err != nil {
+ // return nil, validators.ValidationErrors(validators.GetErrors(err))
+ //}
+
+ url := "/sdn/segment/list"
+
+ res, err := s.client.DecortApiCall(ctx, http.MethodGet, url, req)
+ return res, err
+}
diff --git a/pkg/sdn/segments/models.go b/pkg/sdn/segments/models.go
new file mode 100644
index 0000000..a0bc13b
--- /dev/null
+++ b/pkg/sdn/segments/models.go
@@ -0,0 +1,136 @@
+package segments
+
+import "time"
+
+// List segments
+type ListSegment []SegmentResponse
+
+// Main information about network segment
+type SegmentResponse 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 uint64 `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 uint64 `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 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"`
+}
diff --git a/pkg/sdn/segments/segments.go b/pkg/sdn/segments/segments.go
new file mode 100644
index 0000000..ded67f7
--- /dev/null
+++ b/pkg/sdn/segments/segments.go
@@ -0,0 +1,18 @@
+// API Actor API for managing SDN segments
+package segments
+
+import (
+ "repository.basistech.ru/BASIS/decort-golang-sdk/interfaces"
+)
+
+// Structure for creating request to segments
+type Segments struct {
+ client interfaces.Caller
+}
+
+// Builder for segments endpoints
+func New(client interfaces.Caller) *Segments {
+ return &Segments{
+ client,
+ }
+}
diff --git a/pkg/sdn/segments/update.go b/pkg/sdn/segments/update.go
new file mode 100644
index 0000000..dece2dd
--- /dev/null
+++ b/pkg/sdn/segments/update.go
@@ -0,0 +1,77 @@
+package segments
+
+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 for updating segment
+type UpdateRequest struct {
+ // ID of segment
+ // Required: true
+ SegmentID string `url:"segment_id" json:"segment_id" validate:"required"`
+
+ // ID of version
+ // Required: true
+ VersionID uint64 `url:"version_id" json:"version_id" validate:"required"`
+
+ // Identifier of the parent access group
+ // Required: true
+ AccessGroupID string `url:"access_group_id" json:"access_group_id" validate:"required"`
+
+ // Detailed description of the segment
+ // Required: true
+ Description string `url:"description" json:"description" validate:"required"`
+
+ // User-friendly name for the segment
+ // 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 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"`
+
+ // DHCP IPv4
+ // Required: false
+ DHCPv4 *DHCPv4ConfigRequest `url:"-" json:"dhcp_v4,omitempty"`
+
+ // DHCP IPv6
+ // Required: false
+ DHCPv6 *DHCPv6ConfigRequest `url:"-" json:"dhcp_v6,omitempty"`
+}
+
+// Update updates segment
+func (s Segments) Update(ctx context.Context, req UpdateRequest) (*SegmentResponse, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return nil, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/sdn/segment/update"
+
+ res, err := s.client.DecortApiCallCtype(ctx, http.MethodPut, url, constants.MIMEJSON, req)
+ if err != nil {
+ return nil, err
+ }
+
+ info := SegmentResponse{}
+
+ err = json.Unmarshal(res, &info)
+ if err != nil {
+ return nil, err
+ }
+
+ return &info, nil
+}
diff --git a/tests/platform_upgrade/cloud_test.go b/tests/platform_upgrade/cloud_test.go
index afcc6e0..d32f776 100644
--- a/tests/platform_upgrade/cloud_test.go
+++ b/tests/platform_upgrade/cloud_test.go
@@ -26,6 +26,13 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/vins"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/zone"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/acsgroups"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/adrspools"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/defsecpolicies"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/segments"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/secpolicies"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/secpolicies/rule"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/logicalports"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/routers"
account_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/account"
audit_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/audit"
@@ -50,6 +57,10 @@ import (
trunk_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/trunk"
vins_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/vins"
zone_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/zone"
+
+ extnet_sdn "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/extnet"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/flips"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/netobjgroups"
)
// TestGetListCloudAPI tests platforms responses vs. json tags of golang structures in cloudapi get/list methods
@@ -901,9 +912,9 @@ func TestGetListSDNAPI(t *testing.T) {
// Users AccessGroups
// list
- listAcsGroups, _ := client.SDN().AccessGroups().List(context.Background(), acsgroups.ListGroupsRequest{})
- if len(listAcsGroups.AccessGroups) > 0 {
- id := listAcsGroups.AccessGroups[0].ID
+ listGroups, _ := client.SDN().AccessGroups().List(context.Background(), acsgroups.ListGroupsRequest{})
+ if len(listGroups.AccessGroups) > 0 {
+ id := listGroups.AccessGroups[0].ID
bytes, err = client.SDN().AccessGroups().UserListRaw(context.Background(), acsgroups.UsersListRequest{AccessGroupID: id})
if err != nil {
t.Error(err)
@@ -913,6 +924,196 @@ func TestGetListSDNAPI(t *testing.T) {
t.Errorf("Can not test users get because acs groups list is empty")
}
+ // DefaultSecurityPolicies
+ // List
+ bytes, err = client.SDN().DefaultSecurityPolicies().ListRaw(context.Background(), defsecpolicies.ListRequest{})
+ if err != nil {
+ t.Error(err)
+ }
+
+ getResult("default security policies list", bytes, defsecpolicies.SecurityPoliciesList{}, t)
+
+ // Address Pools
+ // List
+ bytes, err = client.SDN().AddressPools().ListRaw(context.Background(), adrspools.ListAddressPoolsRequest{})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Address pools list", bytes, adrspools.AddressPoolsList{}, t)
+ // Get
+ listStoragePolicies, _ := client.SDN().AddressPools().List(context.Background(), adrspools.ListAddressPoolsRequest{})
+ if len(listStoragePolicies.Pools) > 0 {
+ id := listStoragePolicies.Pools[0].ID
+ bytes, err = client.SDN().AddressPools().GetRaw(context.Background(), adrspools.GetRequest{ID: id})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Address pools get", bytes, adrspools.NetworkAddressPool{}, t)
+ } else {
+ t.Errorf("Can not test address pools get because list is empty")
+ }
+
+ // Security policies
+ // List
+ bytes, err = client.SDN().SecurityPolicies().ListRaw(context.Background(), secpolicies.ListRequest{})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Security policies list", bytes, secpolicies.SecurityPolicyList{}, t)
+ // Get
+ listPolicies, _ := client.SDN().SecurityPolicies().List(context.Background(), secpolicies.ListRequest{})
+ if len(listPolicies) > 0 {
+ id := listPolicies[0].ID
+ bytes, err = client.SDN().SecurityPolicies().GetRaw(context.Background(), secpolicies.GetRequest{ID: id})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Security policies get", bytes, secpolicies.SecurityPolicySummary{}, t)
+ } else {
+ t.Errorf("Can not test security policies get because list is empty")
+ }
+
+ // rule List
+ bytes, err = client.SDN().SecurityPolicies().Rule().ListRaw(context.Background(), rule.ListRequest{})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Security policy rule list", bytes, rule.SecurityRulesList{}, t)
+ // Get
+ listRules, _ := client.SDN().SecurityPolicies().Rule().List(context.Background(), rule.ListRequest{})
+ if len(listRules) > 0 {
+ id := listRules[0].ID
+ securityPolicyID := listRules[0].SecurityPolicyID
+ bytes, err = client.SDN().SecurityPolicies().Rule().GetRaw(context.Background(), rule.GetRequest{
+ SecurityPolicyID: securityPolicyID,
+ SecurityRuleID: id})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Security policy rule get", bytes, rule.SecurityRule{}, t)
+ } else {
+ t.Errorf("Can not test the security policy rules get because list is empty")
+ }
+
+ // ExtNet
+ // List
+ bytes, err = client.SDN().ExtNet().ListRaw(context.Background(), extnet_sdn.ListRequest{})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("ExtNet list", bytes, extnet_sdn.ListExtNet{}, t)
+ // Get
+ listExtNet, _ := client.SDN().ExtNet().List(context.Background(), extnet_sdn.ListRequest{})
+ if len(listExtNet) > 0 {
+ id := listExtNet[0].ID
+ bytes, err = client.SDN().ExtNet().GetRaw(context.Background(), extnet_sdn.GetRequest{ExtNetID: id})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("ExtNet get", bytes, extnet_sdn.ExternalNetworkResponse{}, t)
+ } else {
+ t.Errorf("Can not test ExtNet get because listExtNet list is empty")
+ }
+
+ // Network object groups
+ // List
+ bytes, err = client.SDN().NetworkObjectGroups().ListRaw(context.Background(), netobjgroups.ListRequest{})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("NetObjGroup list", bytes, netobjgroups.NetObjGroupList{}, t)
+ // Get
+ listNetObjGroups, _ := client.SDN().NetworkObjectGroups().List(context.Background(), netobjgroups.ListRequest{})
+ if len(listNetObjGroups.Objects) > 0 {
+ id := listNetObjGroups.Objects[0].ID
+ bytes, err = client.SDN().NetworkObjectGroups().GetRaw(context.Background(), netobjgroups.GetRequest{NetObjGroupID: id})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("NetObjGroup get", bytes, netobjgroups.RecordNetObjGroup{}, t)
+ } else {
+ t.Errorf("Can not test NetObjGroup get because listNetObjGroups list is empty")
+ }
+
+ // Segment
+ // List
+ bytes, err = client.SDN().Segments().ListRaw(context.Background(), segments.ListRequest{})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Segments list", bytes, segments.ListSegment{}, t)
+ // Get
+ listSegments, _ := client.SDN().Segments().List(context.Background(), segments.ListRequest{})
+ if len(listSegments) > 0 {
+ id := listSegments[0].ID
+ bytes, err = client.SDN().Segments().GetRaw(context.Background(), segments.GetRequest{SegmentID: id})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Segments get", bytes, adrspools.NetworkAddressPool{}, t)
+ } else {
+ t.Errorf("Can not test segments get because list is empty")
+ }
+
+ // FloatingIPs
+ // List
+ bytes, err = client.SDN().FloatingIPs().ListRaw(context.Background(), flips.ListRequest{})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("FloatingIPs list", bytes, flips.FloatingIPsList{}, t)
+ // Get
+ listFloatingIPs, _ := client.SDN().FloatingIPs().List(context.Background(), flips.ListRequest{})
+ if len(listFloatingIPs.Objects) > 0 {
+ id := listFloatingIPs.Objects[0].ID
+ bytes, err = client.SDN().FloatingIPs().GetRaw(context.Background(), flips.GetRequest{FloatingIPID: id})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("FloatingIPs get", bytes, flips.RecordFloatingIP{}, t)
+ } else {
+ t.Errorf("Can not test FloatingIPs get because listFloatingIPs list is empty")
+ }
+
+ // logical ports
+ // List
+ bytes, err = client.SDN().LogicalPorts().ListRaw(context.Background(), logicalports.ListRequest{})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Logical ports list", bytes, logicalports.LogicalPortsList{}, t)
+ // Get
+ listLP, _ := client.SDN().LogicalPorts().List(context.Background(), logicalports.ListRequest{})
+ if len(listLP.Ports) > 0 {
+ id := listStoragePolicies.Pools[0].ID
+ bytes, err = client.SDN().LogicalPorts().GetRaw(context.Background(), logicalports.GetRequest{ID: id})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Logical port get", bytes, logicalports.LogicalPort{}, t)
+ } else {
+ t.Errorf("Can not test logical ports get because list is empty")
+ }
+
+ // Routers
+ // List
+ bytes, err = client.SDN().Routers().ListRaw(context.Background(), routers.ListRequest{})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Routers list", bytes, routers.RoutersList{}, t)
+ // Get
+ routerList, _ := client.SDN().Routers().List(context.Background(), routers.ListRequest{})
+ if len(routerList) > 0 {
+ id := routerList[0].ID
+ bytes, err = client.SDN().Routers().GetRaw(context.Background(), routers.GetRequest{ID: id})
+ if err != nil {
+ t.Error(err)
+ }
+ getResult("Router get", bytes, routers.RoutersModel{}, t)
+ } else {
+ t.Errorf("Can not test routers get because routerList list is empty")
+ }
}
// TestRequestsCloudAPI tests platform requests vs. golang request structures in sdk for cloudapi requests
@@ -949,7 +1150,7 @@ func TestGetAllPaths(t *testing.T) {
for _, r := range missingUrls {
errorText += fmt.Sprintln(r)
}
- t.Errorf(errorText)
+ t.Errorf("\n%s\n", errorText)
}
deprecatedUrls := getDeprecatedDecortUrls(jsonUrls, decortUrls)
@@ -958,7 +1159,7 @@ func TestGetAllPaths(t *testing.T) {
for _, r := range deprecatedUrls {
errorText += fmt.Sprintln(r)
}
- t.Errorf(errorText)
+ t.Errorf("\n%s\n", errorText)
}
caTestUrls := getRequestsMapCloudAPI()
@@ -987,7 +1188,7 @@ func TestGetAllPaths(t *testing.T) {
for _, r := range missingUrls {
errorText += fmt.Sprintln(r)
}
- t.Errorf(errorText)
+ t.Errorf("\n%s\n", errorText)
}
deprecatedUrls = getDeprecatedDecortUrls(jsonUrls, urlsInTest)
@@ -996,7 +1197,7 @@ func TestGetAllPaths(t *testing.T) {
for _, r := range deprecatedUrls {
errorText += fmt.Sprintln(r)
}
- t.Errorf(errorText)
+ t.Errorf("\n%s\n", errorText)
}
}
diff --git a/tests/platform_upgrade/request_map.go b/tests/platform_upgrade/request_map.go
index d70ed26..57fbe7c 100644
--- a/tests/platform_upgrade/request_map.go
+++ b/tests/platform_upgrade/request_map.go
@@ -29,6 +29,15 @@ import (
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/vins"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudapi/zone"
"repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/acsgroups"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/adrspools"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/defsecpolicies"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/segments"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/secpolicies"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/secpolicies/rule"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/logicalports"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/routers"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/routers/gwport"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/routers/policies"
account_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/account"
apiaccess_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/apiaccess"
@@ -61,6 +70,10 @@ import (
vgpu_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/vgpu"
vins_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/vins"
zone_cb "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/cloudbroker/zone"
+
+ extnet_sdn "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/extnet"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/netobjgroups"
+ "repository.basistech.ru/BASIS/decort-golang-sdk/pkg/sdn/flips"
)
type EmptyStruct struct{}
@@ -1078,6 +1091,16 @@ func getRequestsMapCloudbroker() map[string]interface{} {
"/restmachine/cloudbroker/zone/update": zone_cb.UpdateRequest{},
"/restmachine/cloudbroker/zone/addNode": zone_cb.AddNodeRequest{},
"/restmachine/cloudbroker/zone/delNode": zone_cb.DelNodeRequest{},
+
+ // security policy
+ "/sdn/security_policy/create": secpolicies.CreateRequest{},
+ "/sdn/security_policy/delete": secpolicies.DeleteRequest{},
+ "/sdn/security_policy/get": secpolicies.GetRequest{},
+ "/sdn/security_policy/list": secpolicies.ListRequest{},
+ "/sdn/security_policy/move": secpolicies.MoveRequest{},
+ "/sdn/security_policy/update": secpolicies.UpdateRequest{},
+ "/sdn/security_policy/rule/get": rule.GetRequest{},
+ "/sdn/security_policy/rule/list": rule.ListRequest{},
}
}
@@ -1086,12 +1109,82 @@ func getRequestsMapSDN() map[string]interface{} {
return map[string]interface{}{
// access_groups
"/restmachine/sdn/access_group/list": acsgroups.ListGroupsRequest{},
+ "/restmachine/sdn/access_group/get": acsgroups.GetGroupRequest{},
"/restmachine/sdn/access_group/user_list": acsgroups.UsersListRequest{},
"/restmachine/sdn/access_group/create": acsgroups.CreateRequest{},
"/restmachine/sdn/access_group/delete": acsgroups.DeleteRequest{},
"/restmachine/sdn/access_group/user_add": acsgroups.UserAddRequest{},
- "/restmachine/sdn/access_group/patch": acsgroups.PatchRequest{},
+ "/restmachine/sdn/access_group/update": acsgroups.UpdateRequest{},
"/restmachine/sdn/access_group/user_delete": acsgroups.UserDeleteRequest{},
"/restmachine/sdn/access_group/update_role": acsgroups.UserUpdateRoleRequest{},
+
+ // address_pools
+ "/restmachine/sdn/address_pool/create": adrspools.CreateRequest{},
+ "/restmachine/sdn/address_pool/delete": adrspools.DeleteRequest{},
+ "/restmachine/sdn/address_pool/get": adrspools.GetRequest{},
+ "/restmachine/sdn/address_pool/list": adrspools.ListAddressPoolsRequest{},
+ "/restmachine/sdn/address_pool/update": adrspools.UpdateRequest{},
+
+ // default security policy
+ "/restmachine/sdn/default_security_policy/list": defsecpolicies.ListRequest{},
+ "/restmachine/sdn/default_security_policy/update": defsecpolicies.UpdateRequest{},
+
+ // extnet
+ "/restmachine/sdn/external_network/port_add": extnet_sdn.PortAddRequest{},
+ "/restmachine/sdn/external_network/port_update": extnet_sdn.PortUpdateRequest{},
+ "/restmachine/sdn/external_network/create": extnet_sdn.CreateRequest{},
+ "/restmachine/sdn/external_network/delete": extnet_sdn.DeleteRequest{},
+ "/restmachine/sdn/external_network/get": extnet_sdn.GetRequest{},
+ "/restmachine/sdn/external_network/list": extnet_sdn.ListRequest{},
+ "/restmachine/sdn/external_network/update": extnet_sdn.UpdateRequest{},
+
+ // floating ips
+ "/restmachine/sdn/floating_ip/create": flips.CreateRequest{},
+ "/restmachine/sdn/floating_ip/delete": flips.DeleteRequest{},
+ "/restmachine/sdn/floating_ip/get": flips.GetRequest{},
+ "/restmachine/sdn/floating_ip/list": flips.ListRequest{},
+ "/restmachine/sdn/floating_ip/update": flips.UpdateRequest{},
+
+ // network object groups
+ "/restmachine/sdn/network_object_group/attach_external_network_ports": netobjgroups.AttachExtNetPortsRequest{},
+ "/restmachine/sdn/network_object_group/attach_logical_ports": netobjgroups.AttachLogicalPortsRequest{},
+ "/restmachine/sdn/network_object_group/create": netobjgroups.CreateRequest{},
+ "/restmachine/sdn/network_object_group/delete": netobjgroups.DeleteRequest{},
+ "/restmachine/sdn/network_object_group/detach_external_network_ports": netobjgroups.DetachExtNetPortsRequest{},
+ "/restmachine/sdn/network_object_group/detach_logical_ports": netobjgroups.DetachLogicalPortsRequest{},
+ "/restmachine/sdn/network_object_group/get": netobjgroups.GetRequest{},
+ "/restmachine/sdn/network_object_group/list": netobjgroups.ListRequest{},
+ "/restmachine/sdn/network_object_group/update": netobjgroups.UpdateRequest{},
+
+ // segments
+ "/restmachine/sdn/segment/create": segments.CreateRequest{},
+ "/restmachine/sdn/segment/delete": segments.DeleteRequest{},
+ "/restmachine/sdn/segment/get": segments.GetRequest{},
+ "/restmachine/sdn/segment/get_status": segments.GetStatusRequest{},
+ "/restmachine/sdn/segment/list": segments.ListRequest{},
+ "/restmachine/sdn/segment/update": segments.UpdateRequest{},
+
+ // logical ports
+ "/restmachine/sdn/logical_port/create": logicalports.CreateRequest{},
+ "/restmachine/sdn/logical_port/delete": logicalports.DeleteRequest{},
+ "/restmachine/sdn/logical_port/get": logicalports.GetRequest{},
+ "/restmachine/sdn/logical_port/get_by_unique_identifier": logicalports.GetByUniqueIdentifierRequest{},
+ "/restmachine/sdn/logical_port/list": logicalports.ListRequest{},
+ "/restmachine/sdn/logical_port/update": logicalports.UpdateRequest{},
+ "/restmachine/sdn/logical_port/migration_cancel": logicalports.MigrateCancelRequest{},
+ "/restmachine/sdn/logical_port/migration_start": logicalports.MigrateStartRequest{},
+
+ // routers
+ "/restmachine/sdn/router/create": routers.CreateRequest{},
+ "/restmachine/sdn/router/delete": extnet_sdn.DeleteRequest{},
+ "/restmachine/sdn/router/gateway_port/create": gwport.CreateRequest{},
+ "/restmachine/sdn/router/gateway_port/delete": gwport.DeleteRequest{},
+ "/restmachine/sdn/router/gateway_port/get": gwport.GetRequest{},
+ "/restmachine/sdn/router/gateway_port/list": gwport.ListRequest{},
+ "/restmachine/sdn/router/gateway_port/update": gwport.UpdateRequest{},
+ "/restmachine/sdn/router/get": routers.GetRequest{},
+ "/restmachine/sdn/router/list": routers.ListRequest{},
+ "/restmachine/sdn/router/policies/list": policies.ListRequest{},
+ "/restmachine/sdn/router/update": routers.UpdateRequest{},
}
}
diff --git a/tests/platform_upgrade/utils_get_list.go b/tests/platform_upgrade/utils_get_list.go
index 3a5d413..82a1694 100644
--- a/tests/platform_upgrade/utils_get_list.go
+++ b/tests/platform_upgrade/utils_get_list.go
@@ -69,7 +69,7 @@ func getResult(testName string, bytes []byte, structure any, t *testing.T) strin
// assert if they are equal
if !reflect.DeepEqual(bytesMap, structMap) {
var result = getDifference(testName, bytesMap, structMap)
- t.Errorf(result)
+ t.Errorf("\n%s\n", result)
return result
}
result := fmt.Sprint(testName, ": ok")