diff --git a/CHANGELOG.md b/CHANGELOG.md
index d1b215c..1d63443 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-## Version 1.13.3
+## Version 1.13.4
Методы `Audits` в cloudapi/compute, cloudbroker/compute, cloudapi/account, cloudbroker/account, cloudapi/vins, cloudbroker/vins, cloudapi/rg и cloudbroker/rg стали deprecated и в следующих версиях будут удалены, вместо них необходимо использовать метод `List` в cloudapi/audit и cloudbroker/audit с соответствующими фильтрами
Метод `ListNodes` в cloudbroker/image стал deprecated и в следующих версиях будет удалён
@@ -12,35 +12,42 @@
#### compute
| Идентификатор
задачи | Описание |
| --- | --- |
-| BGOS-713 | Опциональное поле `NetMap` в структуру запроса `StartMigrationOutRequest` в cloudbroker/compute |
+| BGOS-732 | Опциональное поле `NetMask` в структуру запроса `NetAttachRequest` в cloudapi/compute |
+| BGOS-727 | Опциональное поле `Cache` в структуру запроса `DiskAddRequest` в cloudapi/compute и в cloudbroker/compute |
+| BGOS-727 | Поле `Cache` в структуры ответа `ItemComputeDisk` в cloudapi/compute и в `ItemDisk` в cloudbroker/compute |
-#### sep
+#### disks
| Идентификатор
задачи | Описание |
| --- | --- |
-| BGOS-712 | Возвращаемый тип методов `AddConsumerNodes` и `AddProviderNodes` изменен с `bool` на `uint64` в cloudbroker/sep |
-| BGOS-722 | Валидатор для поля `SEPType` в структуре запроса `CreateRequest` в cloudbroker/sep |
+| BGOS-725 | Структура запроса `UpdateRequest`, метод `Update` в cloudbroker/disks |
+| BGOS-725 | Опциональное поле `Cache` в структуру запроса `CreateRequest` в cloudapi/disks и в cloudbroker/disks |
+| BGOS-725 | Поле `Cache` в структуру ответа `InfoDisk` в cloudbroker/disks |
+| BGOS-725 | Поле `Cache` в структуры ответа `RecordDisk`,`ItemDisk` в cloudapi/disks |
+
+#### kvmx86
+| Идентификатор
задачи | Описание |
+| --- | --- |
+| BGOS-727 | Опциональное поле `BootDiskCache` в структуры запроса `CreateRequest`, `CreateBlankRequest` в cloudapi/kvmx86 и в cloudbroker/kvmx86 |
+| BGOS-727 | Опциональное поле `BootDiskCache` в структуру запроса `MassCreateRequest` в cloudbroker/kvmx86 |
### Изменено
-#### compute
+#### bservice
| Идентификатор
задачи | Описание |
| --- | --- |
-| BGOS-721 | Тип поля `StoragePolicyID` изменен с опционального на обязательный в структуре запроса `CloneRequest` cloudbroker/compute |
+| BGOS-728 | Тип полей `Chipset` и `Mode` с обязательного на опциональный в структуре запроса `GroupResizeRequest` в cloudapi/bservice и в cloudbroker/bservice |
-#### grid
+#### lb
| Идентификатор
задачи | Описание |
| --- | --- |
-| BGOS-719 | Поле `Age` в структуре запроса `PurgeLogsRequest` стало необязательным в cloudbroker/grid |
+| BGOS-733 | Тип поля `Start` с обязательного на опциональный в структуре запроса `CreateRequest`в cloudapi/lb и в cloudbroker/lb |
-#### storage policy
+#### trunk
| Идентификатор
задачи | Описание |
| --- | --- |
-| BGOS-720 | Тип поля `AccessSEPsPools` изменен с обязательного на опциональный в структуре запроса `UpdateRequest` в cloudbroker/stpolicy |
+| BGOS-730 | Тип полей `Name` и `TrunkTags` с обязательного на опциональный в структуре запроса `UpdateRequest` в cloudbroker/trunk |
-
-### Удалено
-
-#### sep
+#### user
| Идентификатор
задачи | Описание |
| --- | --- |
-| BGOS-712 | Поля `ProviderNIDs` и `ConsumerNIDs` удалены из структуры запроса `CreateRequest` в cloudbroker/sep |
+| BGOS-729 | Тип поля `Limit` с обязательного на опциональный в структуре запроса `GetMatchingUsernamesRequest` в cloudbroker/user |
\ No newline at end of file
diff --git a/pkg/cloudapi/bservice/group_resize.go b/pkg/cloudapi/bservice/group_resize.go
index 84657f9..32d1fd1 100644
--- a/pkg/cloudapi/bservice/group_resize.go
+++ b/pkg/cloudapi/bservice/group_resize.go
@@ -25,15 +25,15 @@ type GroupResizeRequest struct {
// Chipset for new computes, either i440fx or Q35 (i440fx by default)
// Available values : i440fx, Q35
// Default value : Q35
- // Required: true
- Chipset string `url:"chipset" json:"chipset" validate:"required,chipset"`
+ // Required: false
+ Chipset string `url:"chipset,omitempty" json:"chipset,omitempty" validate:"omitempty,chipset"`
// Either delta or absolute value of computes
// Should be one of:
// - ABSOLUTE
// - RELATIVE
- // Required: true
- Mode string `url:"mode" json:"mode" validate:"bserviceMode"`
+ // Required: false
+ Mode string `url:"mode,omitempty" json:"mode,omitempty" validate:"omitempty,bserviceMode"`
}
// GroupResize resize the group by changing the number of computes
diff --git a/pkg/cloudapi/compute/disk_add.go b/pkg/cloudapi/compute/disk_add.go
index 13f4d86..9060bd6 100644
--- a/pkg/cloudapi/compute/disk_add.go
+++ b/pkg/cloudapi/compute/disk_add.go
@@ -58,6 +58,10 @@ type DiskAddRequest struct {
// Desired bus number (hex string, e.g. "0x03")
// Required: false
BusNumber string `url:"bus_number,omitempty" json:"bus_number,omitempty"`
+
+ // Disk cache mode
+ // Required: false
+ Cache string `url:"cache,omitempty" json:"cache,omitempty"`
}
// DiskAdd creates new disk and attach to compute
diff --git a/pkg/cloudapi/compute/models.go b/pkg/cloudapi/compute/models.go
index c029268..0691c59 100644
--- a/pkg/cloudapi/compute/models.go
+++ b/pkg/cloudapi/compute/models.go
@@ -795,6 +795,9 @@ type ItemComputeDisk struct {
// Bus number
BusNumber uint64 `json:"bus_number"`
+ // Chache
+ Cache string `json:"cache"`
+
// Created by
CreatedBy string `json:"createdBy"`
diff --git a/pkg/cloudapi/compute/net_attach.go b/pkg/cloudapi/compute/net_attach.go
index 0f3ccce..5d9870f 100644
--- a/pkg/cloudapi/compute/net_attach.go
+++ b/pkg/cloudapi/compute/net_attach.go
@@ -45,6 +45,11 @@ type NetAttachRequest struct {
// Required: false
MTU uint64 `url:"mtu,omitempty" json:"mtu,omitempty" validate:"omitempty,mtu"`
+ // Net mask
+ // Used only to DPDK or VFNIC net type
+ // Required: false
+ NetMask uint64 `url:"netMask,omitempty" json:"netMask,omitempty"`
+
// Unique identifier of logical port on SDN side
// Required: false
SDNInterfaceID string `url:"sdn_interface_id,omitempty" json:"sdn_interface_id,omitempty" validate:"omitempty"`
diff --git a/pkg/cloudapi/disks/create.go b/pkg/cloudapi/disks/create.go
index f4e3dfb..b176a17 100644
--- a/pkg/cloudapi/disks/create.go
+++ b/pkg/cloudapi/disks/create.go
@@ -37,6 +37,10 @@ type CreateRequest struct {
// Pool name to create disk
// Required: false
Pool string `url:"pool,omitempty" json:"pool,omitempty"`
+
+ // Cache mode of disk
+ // Required: false
+ Cache string `url:"cache,omitempty" json:"cache,omitempty"`
}
// Create creates a disk
diff --git a/pkg/cloudapi/disks/models.go b/pkg/cloudapi/disks/models.go
index db67195..430f414 100644
--- a/pkg/cloudapi/disks/models.go
+++ b/pkg/cloudapi/disks/models.go
@@ -139,6 +139,9 @@ type ItemDisk struct {
// Updated by
UpdatedBy string `json:"updatedBy"`
+
+ // Cache mode of disk
+ Cache string `json:"cache"`
}
type ItemDiskUnattached struct {
@@ -503,6 +506,9 @@ type RecordDisk struct {
// Updated by
UpdatedBy string `json:"updatedBy"`
+
+ // Cache mode of disk
+ Cache string `json:"cache"`
}
type ItemReplication struct {
diff --git a/pkg/cloudapi/kvmx86/create.go b/pkg/cloudapi/kvmx86/create.go
index 02ed108..5158a74 100644
--- a/pkg/cloudapi/kvmx86/create.go
+++ b/pkg/cloudapi/kvmx86/create.go
@@ -209,6 +209,10 @@ type CreateRequest struct {
// Zone ID
// Required: false
ZoneID uint64 `url:"zoneId,omitempty" json:"zoneId,omitempty"`
+
+ // Cache mode for boot disk
+ // Required: false
+ BootDiskCache string `url:"boot_disk_cache,omitempty" json:"boot_disk_cache,omitempty"`
}
// GetRAM returns RAM field values
diff --git a/pkg/cloudapi/kvmx86/create_blank.go b/pkg/cloudapi/kvmx86/create_blank.go
index 22e3596..13db7bb 100644
--- a/pkg/cloudapi/kvmx86/create_blank.go
+++ b/pkg/cloudapi/kvmx86/create_blank.go
@@ -115,6 +115,10 @@ type CreateBlankRequest struct {
// The OS version that will be installed on the virtual machine
// Required: false
OSVersion string `url:"os_version,omitempty" json:"os_version,omitempty"`
+
+ // Cache mode for boot disk
+ // Required: false
+ BootDiskCache string `url:"boot_disk_cache,omitempty" json:"boot_disk_cache,omitempty"`
}
// GetRAM returns RAM field values
diff --git a/pkg/cloudapi/lb/create.go b/pkg/cloudapi/lb/create.go
index 513ab91..e8ec19f 100644
--- a/pkg/cloudapi/lb/create.go
+++ b/pkg/cloudapi/lb/create.go
@@ -38,8 +38,8 @@ type CreateRequest struct {
HighlyAvailable bool `url:"highlyAvailable,omitempty" json:"highlyAvailable,omitempty"`
// Start now Load balancer
- // Required: true
- Start bool `url:"start" json:"start" validate:"required"`
+ // Required: false
+ Start interface{} `url:"start,omitempty" json:"start,omitempty" validate:"omitempty,isBool"`
// Text description of this load balancer
// Required: false
diff --git a/pkg/cloudbroker/bservice/group_resize.go b/pkg/cloudbroker/bservice/group_resize.go
index c7fa9ea..ce790da 100644
--- a/pkg/cloudbroker/bservice/group_resize.go
+++ b/pkg/cloudbroker/bservice/group_resize.go
@@ -22,18 +22,18 @@ type GroupResizeRequest struct {
// Required: true
Count int64 `url:"count" json:"count" validate:"required"`
- //Chipset for new computes, either i440fx or Q35 (i440fx by default)
- //Available values : i440fx, Q35
- //Default value : Q35
- //Required: true
- Chipset string `url:"chipset" json:"chipset" validate:"required,chipset"`
+ // Chipset for new computes, either i440fx or Q35 (i440fx by default)
+ // Available values : i440fx, Q35
+ // Default value : Q35
+ // Required: false
+ Chipset string `url:"chipset,omitempty" json:"chipset,omitempty" validate:"omitempty,chipset"`
// Either delta or absolute value of computes
// Should be one of:
// - ABSOLUTE
// - RELATIVE
- // Required: true
- Mode string `url:"mode" json:"mode" validate:"bserviceMode"`
+ // Required: false
+ Mode string `url:"mode,omitempty" json:"mode,omitempty" validate:"omitempty,bserviceMode"`
}
// GroupResize resize the group by changing the number of computes
diff --git a/pkg/cloudbroker/compute/disk_add.go b/pkg/cloudbroker/compute/disk_add.go
index 1fe3f46..3fde790 100644
--- a/pkg/cloudbroker/compute/disk_add.go
+++ b/pkg/cloudbroker/compute/disk_add.go
@@ -58,6 +58,10 @@ type DiskAddRequest struct {
// Desired bus number (hex string, e.g. "0x03")
// Required: false
BusNumber string `url:"bus_number,omitempty" json:"bus_number,omitempty"`
+
+ // Disk cache mode
+ // Required: false
+ Cache string `url:"cache,omitempty" json:"cache,omitempty"`
}
// DiskAdd creates new disk and attach to compute
diff --git a/pkg/cloudbroker/compute/models.go b/pkg/cloudbroker/compute/models.go
index 76b9ff4..9af170a 100644
--- a/pkg/cloudbroker/compute/models.go
+++ b/pkg/cloudbroker/compute/models.go
@@ -397,6 +397,9 @@ type ItemDisk struct {
// Bus number
BusNumber uint64 `json:"bus_number"`
+ // Chache
+ Cache string `json:"cache"`
+
// Created by
CreatedBy string `json:"createdBy"`
@@ -1173,7 +1176,7 @@ type RecordCompute struct {
ZoneID uint64 `json:"zoneId"`
//todo
- _ uint64 `json:"nodeId"`
+ _ uint64 `json:"nodeId"`
}
type LoaderMetaIso struct {
diff --git a/pkg/cloudbroker/disks/create.go b/pkg/cloudbroker/disks/create.go
index 9c9cb71..ac89920 100644
--- a/pkg/cloudbroker/disks/create.go
+++ b/pkg/cloudbroker/disks/create.go
@@ -37,6 +37,10 @@ type CreateRequest struct {
// Pool name to create disk
// Required: false
Pool string `url:"pool,omitempty" json:"pool,omitempty"`
+
+ // Cache mode of disk
+ // Required: false
+ Cache string `url:"cache,omitempty" json:"cache,omitempty"`
}
// Create creates a disk
diff --git a/pkg/cloudbroker/disks/models.go b/pkg/cloudbroker/disks/models.go
index 1ff9471..35575bc 100644
--- a/pkg/cloudbroker/disks/models.go
+++ b/pkg/cloudbroker/disks/models.go
@@ -187,6 +187,9 @@ type InfoDisk struct {
// Updated by
UpdatedBy string `json:"updatedBy"`
+
+ // Cache mode of disk
+ Cache string `json:"cache"`
}
type ItemReplication struct {
diff --git a/pkg/cloudbroker/disks/update.go b/pkg/cloudbroker/disks/update.go
new file mode 100644
index 0000000..9c38734
--- /dev/null
+++ b/pkg/cloudbroker/disks/update.go
@@ -0,0 +1,42 @@
+package disks
+
+import (
+ "context"
+ "net/http"
+ "strconv"
+
+ "repository.basistech.ru/BASIS/decort-golang-sdk/internal/validators"
+)
+
+// UpdateRequest struct to update disk
+type UpdateRequest struct {
+ // ID of the disk to update
+ // Required: true
+ DiskID uint64 `url:"disk_id" json:"disk_id" validate:"required"`
+
+ // Cache mode of disk
+ // Required: false
+ Cache string `url:"cache,omitempty" json:"cache,omitempty"`
+}
+
+// Update updates disk
+func (d Disks) Update(ctx context.Context, req UpdateRequest) (bool, error) {
+ err := validators.ValidateRequest(req)
+ if err != nil {
+ return false, validators.ValidationErrors(validators.GetErrors(err))
+ }
+
+ url := "/cloudbroker/disks/update"
+
+ res, err := d.client.DecortApiCall(ctx, http.MethodPost, url, req)
+ if err != nil {
+ return false, err
+ }
+
+ result, err := strconv.ParseBool(string(res))
+ if err != nil {
+ return false, err
+ }
+
+ return result, nil
+}
diff --git a/pkg/cloudbroker/kvmx86/create.go b/pkg/cloudbroker/kvmx86/create.go
index 7da961f..5015a3c 100644
--- a/pkg/cloudbroker/kvmx86/create.go
+++ b/pkg/cloudbroker/kvmx86/create.go
@@ -211,6 +211,10 @@ type CreateRequest struct {
// Zone ID
// Required: false
ZoneID uint64 `url:"zoneId,omitempty" json:"zoneId,omitempty"`
+
+ // Cache mode for boot disk
+ // Required: false
+ BootDiskCache string `url:"boot_disk_cache,omitempty" json:"boot_disk_cache,omitempty"`
}
// GetRAM returns RAM field values
diff --git a/pkg/cloudbroker/kvmx86/create_blank.go b/pkg/cloudbroker/kvmx86/create_blank.go
index 73c49ed..94777f8 100644
--- a/pkg/cloudbroker/kvmx86/create_blank.go
+++ b/pkg/cloudbroker/kvmx86/create_blank.go
@@ -115,6 +115,10 @@ type CreateBlankRequest struct {
// The OS version that will be installed on the virtual machine
// Required: false
OSVersion string `url:"os_version,omitempty" json:"os_version,omitempty"`
+
+ // Cache mode for boot disk
+ // Required: false
+ BootDiskCache string `url:"boot_disk_cache,omitempty" json:"boot_disk_cache,omitempty"`
}
// GetRAM returns RAM field values
diff --git a/pkg/cloudbroker/kvmx86/mass_create.go b/pkg/cloudbroker/kvmx86/mass_create.go
index d0850ee..8972a43 100644
--- a/pkg/cloudbroker/kvmx86/mass_create.go
+++ b/pkg/cloudbroker/kvmx86/mass_create.go
@@ -132,6 +132,10 @@ type MassCreateRequest struct {
// The OS version that will be installed on the virtual machine
// Required: false
OSVersion string `url:"os_version,omitempty" json:"os_version,omitempty"`
+
+ // Cache mode for boot disk
+ // Required: false
+ BootDiskCache string `url:"boot_disk_cache,omitempty" json:"boot_disk_cache,omitempty"`
}
type asyncWrapperMassCreateRequest struct {
diff --git a/pkg/cloudbroker/lb/create.go b/pkg/cloudbroker/lb/create.go
index 13d9055..6e0a7db 100644
--- a/pkg/cloudbroker/lb/create.go
+++ b/pkg/cloudbroker/lb/create.go
@@ -30,8 +30,8 @@ type CreateRequest struct {
VINSID uint64 `url:"vinsId" json:"vinsId"`
// Start now Load balancer
- // Required: true
- Start bool `url:"start" json:"start" validate:"required"`
+ // Required: false
+ Start interface{} `url:"start,omitempty" json:"start,omitempty" validate:"omitempty,isBool"`
// Custom sysctl values for Load Balancer instance. Applied on boot
// Required: false
diff --git a/pkg/cloudbroker/trunk/update.go b/pkg/cloudbroker/trunk/update.go
index 1d060ea..c114a2a 100644
--- a/pkg/cloudbroker/trunk/update.go
+++ b/pkg/cloudbroker/trunk/update.go
@@ -15,12 +15,12 @@ type UpdateRequest struct {
TrunkID uint64 `url:"id" json:"id" validate:"required"`
// New name of the trunk
- // Required: true
- Name string `url:"name" json:"name" validate:"required"`
+ // Required: false
+ Name string `url:"name,omitempty" json:"name,omitempty"`
// List of trunk tags (values between 1-4095)
- // Required: true
- TrunkTags string `url:"trunk_tags" json:"trunk_tags" validate:"required,trunkTags"`
+ // Required: false
+ TrunkTags string `url:"trunk_tags,omitempty" json:"trunk_tags,omitempty" validate:"omitempty,trunkTags"`
// New description of the trunk
// Required: false
diff --git a/pkg/cloudbroker/user/get_matching_usernames.go b/pkg/cloudbroker/user/get_matching_usernames.go
index 9ff5c84..2145624 100644
--- a/pkg/cloudbroker/user/get_matching_usernames.go
+++ b/pkg/cloudbroker/user/get_matching_usernames.go
@@ -15,8 +15,8 @@ type GetMatchingUsernamesRequest struct {
UsernameRegex string `url:"usernameregex" json:"usernameregex" validate:"required"`
// The number of usernames to return.
- // Required: true
- Limit uint64 `url:"limit" json:"limit" validate:"required"`
+ // Required: false
+ Limit uint64 `url:"limit,omitempty" json:"limit,omitempty"`
}
// GetMatchingUsernames gets a list of the matching usernames for a given string.
diff --git a/tests/platform_upgrade/request_map.go b/tests/platform_upgrade/request_map.go
index 1b3c06e..5f44c3b 100644
--- a/tests/platform_upgrade/request_map.go
+++ b/tests/platform_upgrade/request_map.go
@@ -693,6 +693,7 @@ func getRequestsMapCloudbroker() map[string]interface{} {
"/restmachine/cloudbroker/disks/migrate": disks_cb.MigrateRequest{},
"/restmachine/cloudbroker/disks/migrate_abort": disks_cb.MigrateAbortRequest{},
"/restmachine/cloudbroker/disks/migrate_status": disks_cb.GetMigrateStatusRequest{},
+ "/restmachine/cloudbroker/disks/update": disks_cb.UpdateRequest{},
// dpdknet
"/restmachine/cloudbroker/dpdknet/get": dpdknet_cb.GetRequest{},
@@ -889,7 +890,7 @@ func getRequestsMapCloudbroker() map[string]interface{} {
"/restmachine/cloudbroker/node/setVFsNumber": node_cb.SetVFsNumberRequest{},
"/restmachine/cloudbroker/node/update": node_cb.UpdateRequest{},
"/restmachine/cloudbroker/node/update_description": node_cb.UpdateDescriptionRequest{},
- "/restmachine/cloudbroker/node/setVFsParams": node_cb.VFParam{},
+ "/restmachine/cloudbroker/node/setVFsParams": node_cb.SetVFsParamsRequest{},
"/restmachine/cloudbroker/node/get_logical_cores_count": node_cb.GetLogicalCoresCountRequest{},
"/restmachine/cloudbroker/node/set_cpu_allocation_ratio": node_cb.SetCpuAllocationRatioRequest{},
"/restmachine/cloudbroker/node/set_mem_allocation_ratio": node_cb.SetMemAllocationRatioRequest{},
diff --git a/tests/platform_upgrade/utils_requests.go b/tests/platform_upgrade/utils_requests.go
index 1380204..65ad118 100644
--- a/tests/platform_upgrade/utils_requests.go
+++ b/tests/platform_upgrade/utils_requests.go
@@ -238,6 +238,25 @@ func getErrorsFromJSON(bytes []byte, t *testing.T, cloud string) {
if len(params) != typStruct.NumField() {
errs = append(errs, fmt.Sprintf("Platform (%d) and golang structure (%d) have different amount of fields.", len(params), typStruct.NumField()))
}
+ paramMap := make(map[string]bool)
+ paramRequiredMap := make(map[string]bool)
+ for _, p := range params {
+ param, ok := p.(map[string]interface{})
+ if !ok {
+ continue
+ }
+ name, ok := param["name"].(string)
+ if ok {
+ paramMap[name] = true
+ required, ok := param["required"].(bool)
+ if ok {
+ paramRequiredMap[name] = required
+ } else {
+ paramRequiredMap[name] = false
+ }
+ }
+ }
+
for _, p := range params {
param, ok := p.(map[string]interface{})
if !ok {
@@ -292,6 +311,33 @@ func getErrorsFromJSON(bytes []byte, t *testing.T, cloud string) {
errs = append(errs, fmt.Sprintf("Platform has field %s that golang structure doesn't", name))
}
}
+
+ // Check if required fields in Go structure are missing from platform JSON
+ // or if they exist but are not required on platform
+ for i := 0; i < typStruct.NumField(); i++ {
+ jsonTag := typStruct.Field(i).Tag.Get("json")
+ validation, _ := typStruct.Field(i).Tag.Lookup("validate")
+
+ fieldName := strings.Split(jsonTag, ",")[0]
+ if fieldName == "" || fieldName == "-" {
+ continue
+ }
+
+ if strings.Contains(validation, "required") {
+ if !paramMap[fieldName] {
+ errs = append(errs, fmt.Sprintf("Golang structure has required field %s that platform doesn't", fieldName))
+ } else {
+ platformRequired := paramRequiredMap[fieldName]
+ if !platformRequired {
+ fieldType := typStruct.Field(i).Type
+ if fieldType.Kind() == reflect.Bool {
+ errs = append(errs, fmt.Sprintf("Golang structure has required field %s that platform doesn't", fieldName))
+ }
+ }
+ }
+ }
+ }
+
if len(errs) > 0 {
msg := fmt.Sprintf("Path %s has following errors: %v", k, errs)
t.Error(msg)