clouapi: test Disk customization validation

Two new tests are added, one with valid customizations and another with
invalid customizations.
Invalid customizations will return one of two error types, depending on
where exactly the failures was generated.
When the openapi schema fails to validate a request, it will return
IMAGE-BUILDLER-30.
When the error occurs while converting the openapi customizations to the
blueprint types, it will have the IMAGE-BUILDER-35 type instead.
This commit is contained in:
Achilleas Koutsou 2025-06-03 14:09:40 +02:00 committed by Tomáš Hozza
parent 45929efbcf
commit 5a5e6d732c

View file

@ -0,0 +1,530 @@
package v2_test
import (
"fmt"
"net/http"
"testing"
"github.com/osbuild/images/pkg/distro/test_distro"
"github.com/osbuild/osbuild-composer/internal/test"
)
func TestComposeDiskCustomizationsValidation(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
testCases := map[string]string{
"simple": `
{
"disk": {
"minsize": "100 GiB",
"partitions": [
{
"mountpoint": "/home",
"fs_type": "ext4",
"minsize": "2 GiB"
}
]
}
}
`,
"simple-with-type": `
{
"disk": {
"minsize": "100 GiB",
"partitions": [
{
"type": "plain",
"mountpoint": "/home",
"fs_type": "xfs",
"minsize": "2 GiB"
}
]
}
}
`,
"large-plain": `
{
"disk": {
"type": "gpt",
"partitions": [
{
"mountpoint": "/data",
"fs_type": "ext4",
"minsize": "1 GiB"
},
{
"mountpoint": "/home",
"label": "home",
"fs_type": "ext4",
"minsize": "2 GiB"
},
{
"mountpoint": "/home/shadowman",
"fs_type": "ext4",
"minsize": "5 GiB"
},
{
"mountpoint": "/var",
"fs_type": "xfs",
"minsize": "4 GiB"
},
{
"mountpoint": "/opt",
"fs_type": "ext4",
"minsize": "10 GiB"
},
{
"mountpoint": "/media",
"fs_type": "ext4",
"minsize": "9 GiB"
},
{
"mountpoint": "/root",
"fs_type": "ext4",
"minsize": "1 GiB"
},
{
"mountpoint": "/srv",
"fs_type": "xfs",
"minsize": "3 GiB"
},
{
"fs_type": "swap",
"minsize": "1 GiB"
}
]
}
}
`,
"lvm": `
{
"disk": {
"type": "gpt",
"partitions": [
{
"mountpoint": "/data",
"minsize": "1 GiB",
"label": "data",
"fs_type": "ext4"
},
{
"type": "lvm",
"name": "testvg",
"minsize": "200 GiB",
"logical_volumes": [
{
"name": "homelv",
"mountpoint": "/home",
"label": "home",
"fs_type": "ext4",
"minsize": "2 GiB"
},
{
"name": "shadowmanlv",
"mountpoint": "/home/shadowman",
"fs_type": "ext4",
"minsize": "5 GiB"
},
{
"name": "foolv",
"mountpoint": "/foo",
"fs_type": "ext4",
"minsize": "1 GiB"
},
{
"name": "usrlv",
"mountpoint": "/usr",
"fs_type": "ext4",
"minsize": "4 GiB"
},
{
"name": "optlv",
"mountpoint": "/opt",
"fs_type": "ext4",
"minsize": "1 GiB"
},
{
"name": "medialv",
"mountpoint": "/media",
"fs_type": "ext4",
"minsize": "10 GiB"
},
{
"name": "roothomelv",
"mountpoint": "/root",
"fs_type": "ext4",
"minsize": "1 GiB"
},
{
"name": "srvlv",
"mountpoint": "/srv",
"fs_type": "ext4",
"minsize": "10 GiB"
},
{
"name": "swap-lv",
"fs_type": "swap",
"minsize": "1 GiB"
}
]
}
]
}
}
`,
"btrfs": `
{
"disk": {
"partitions": [
{
"type": "plain",
"mountpoint": "/data",
"minsize": "12 GiB",
"fs_type": "xfs"
},
{
"type": "btrfs",
"minsize": "10 GiB",
"subvolumes": [
{
"name": "subvol-home",
"mountpoint": "/home"
},
{
"name": "subvol-shadowman",
"mountpoint": "/home/shadowman"
},
{
"name": "subvol-foo",
"mountpoint": "/foo"
},
{
"name": "subvol-usr",
"mountpoint": "/usr"
},
{
"name": "subvol-opt",
"mountpoint": "/opt"
},
{
"name": "subvol-media",
"mountpoint": "/media"
},
{
"name": "subvol-root",
"mountpoint": "/root"
},
{
"name": "subvol-srv",
"mountpoint": "/srv"
}
]
},
{
"type": "plain",
"fs_type": "swap",
"label": "swap-part",
"minsize": "1 GiB"
}
]
}
}
`,
}
resp := `{"href": "/api/image-builder-composer/v2/compose", "kind": "ComposeId"}`
for name, customizations := range testCases {
t.Run(name, func(t *testing.T) {
body := fmt.Sprintf(`
{
"distribution": "%s",
"customizations": %s,
"image_request":{
"architecture": "%s",
"image_type": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, test_distro.TestDistro1Name, customizations, test_distro.TestArch3Name)
test.TestRouteWithReply(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", body, http.StatusCreated, resp, "id")
})
}
}
func TestComposeDiskCustomizationsErrors(t *testing.T) {
srv, _, _, cancel := newV2Server(t, t.TempDir(), false, false)
defer cancel()
type testCase struct {
customizations string
expResponse string
}
// one of two responses is returned depending on the exact origin of the error:
// 1. IMAGE-BUILDER-COMPOSER-30 is returned when the customization failed at the schema level, when it fails to validate against the openapi schema
// 2. IMAGE-BUILDER-COMPOSER-35 is returned when the customization failed the Disk.Validate() call at the blueprint level
validationErrorResp := `{"href": "/api/image-builder-composer/v2/errors/30", "kind": "Error", "reason": "Request could not be validated", "code": "IMAGE-BUILDER-COMPOSER-30"}`
invalidCustomizationResp := `{"href": "/api/image-builder-composer/v2/errors/35", "kind": "Error", "reason": "Invalid image customization", "code": "IMAGE-BUILDER-COMPOSER-35"}`
testCases := map[string]testCase{
"empty": {
customizations: `{"disk": {}}`,
expResponse: validationErrorResp,
},
"number-minsize": {
customizations: `
{
"disk": {
"minsize": 1024,
"partitions": [
{
"mountpoint": "/",
"minsize": "2 GiB",
"fs_type": "ext4"
}
]
}
}
`,
expResponse: validationErrorResp,
},
"no-fs-type": {
customizations: `
{
"disk": {
"partitions": [
{
"mountpoint": "/",
"minsize": "2 GiB"
}
]
}
}
`,
expResponse: validationErrorResp,
},
"swap-with-mountpoint": {
customizations: `
{
"disk": {
"partitions": [
{
"mountpoint": "/",
"fs_type": "swap"
}
]
}
}
`,
expResponse: invalidCustomizationResp,
},
"nonswap-without-mountpoint": {
customizations: `
{
"disk": {
"partitions": [
{
"fs_type": "xfs"
}
]
}
}
`,
expResponse: invalidCustomizationResp,
},
"swap-with-mountpoint-lvm": {
customizations: `
{
"disk": {
"partitions": [
{
"type": "lvm",
"logical_volumes": [{
"mountpoint": "/",
"fs_type": "swap"
}]
}
]
}
}
`,
expResponse: invalidCustomizationResp,
},
"nonswap-without-mountpoint-lvm": {
customizations: `
{
"disk": {
"partitions": [
{
"type": "lvm",
"logical_volumes": [{
"fs_type": "xfs"
}]
}
]
}
}
`,
expResponse: invalidCustomizationResp,
},
"notype-with-lv": {
customizations: `
{
"disk": {
"partitions": [
{
"logical_volumes": [
{
"name": "homelv",
"mountpoint": "/home",
"label": "home",
"fs_type": "ext4",
"minsize": "2 GiB"
}
]
}
]
}
}
`,
expResponse: validationErrorResp,
},
"plain-with-lv": {
customizations: `
{
"disk": {
"partitions": [
{
"type": "plain",
"logical_volumes": [
{
"name": "homelv",
"mountpoint": "/home",
"label": "home",
"fs_type": "ext4",
"minsize": "2 GiB"
}
]
}
]
}
}
`,
expResponse: validationErrorResp,
},
"notype-with-subvol": {
customizations: `
{
"disk": {
"partitions": [
{
"subvolumes": [
{
"name": "home",
"mountpoint": "/home"
}
]
}
]
}
}
`,
expResponse: validationErrorResp,
},
"plain-with-subvol": {
customizations: `
{
"disk": {
"partitions": [
{
"type": "plain",
"subvolumes": [
{
"name": "home",
"mountpoint": "/home"
}
]
}
]
}
}
`,
expResponse: validationErrorResp,
},
"lvm-with-subvol": {
customizations: `
{
"disk": {
"partitions": [
{
"type": "lvm",
"subvolumes": [
{
"name": "home",
"mountpoint": "/home"
}
]
}
]
}
}
`,
expResponse: validationErrorResp,
},
"btrfs-with-lv": {
customizations: `
{
"disk": {
"partitions": [
{
"type": "btrfs",
"logical_volumes": [
{
"name": "homelv",
"mountpoint": "/home",
"label": "home",
"fs_type": "ext4",
"minsize": "2 GiB"
}
]
}
]
}
}
`,
expResponse: validationErrorResp,
},
}
for name, tc := range testCases {
t.Run(name, func(t *testing.T) {
body := fmt.Sprintf(`
{
"distribution": "%s",
"customizations": %s,
"image_request":{
"architecture": "%s",
"image_type": "aws",
"repositories": [{
"baseurl": "somerepo.org",
"rhsm": false
}],
"upload_options": {
"region": "eu-central-1"
}
}
}`, test_distro.TestDistro1Name, tc.customizations, test_distro.TestArch3Name)
test.TestRoute(t, srv.Handler("/api/image-builder-composer/v2"), false, "POST", "/api/image-builder-composer/v2/compose", body, http.StatusBadRequest, tc.expResponse, "id", "operation_id", "details")
})
}
}