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")