From 813f767bbb6814f9dbef56cab792599867dc210f Mon Sep 17 00:00:00 2001 From: Kelly Deng Date: Mon, 15 Jun 2020 17:25:08 -0400 Subject: [PATCH] tests wip --- data_fetch.go | 177 +++++++++++++++++++++++++++++++++++++++++++- grpc_server.go | 128 ++++++++++++++++++++++++++++++-- grpc_server_test.go | 172 ++++++++++++++++++++++++++++++++++-------- 3 files changed, 440 insertions(+), 37 deletions(-) diff --git a/data_fetch.go b/data_fetch.go index 97391d64..ae59e28e 100644 --- a/data_fetch.go +++ b/data_fetch.go @@ -18,6 +18,7 @@ const ( "allow_workflow": true, "plan_slug": "t1.small.x86", "facility_code": "onprem", + "efi_boot": false, "instance": { "storage": { "disks": [ @@ -95,6 +96,126 @@ const ( } ] } +` + cacherPartitionSizeInt = ` + { + "id": "8978e7d4-1a55-4845-8a66-a5259236b104", + "instance": { + "storage": { + "disks": [ + { + "partitions": [ + { + "size": 4096, + "label": "BIOS", + "number": 1 + } + ] + } + ] + } + } + } +` + cacherPartitionSizeString = ` + { + "id": "8978e7d4-1a55-4845-8a66-a5259236b104", + "instance": { + "storage": { + "disks": [ + { + "partitions": [ + { + "size": "3333", + "label": "BIOS", + "number": 1 + } + ] + } + ] + } + } + } +` + cacherPartitionSizeWhitespace = ` + { + "id": "8978e7d4-1a55-4845-8a66-a5259236b104", + "instance": { + "storage": { + "disks": [ + { + "partitions": [ + { + "size": " 1234 ", + "label": "BIOS", + "number": 1 + } + ] + } + ] + } + } + } +` + cacherPartitionSizeK = ` + { + "id": "8978e7d4-1a55-4845-8a66-a5259236b104", + "instance": { + "storage": { + "disks": [ + { + "partitions": [ + { + "size": "24K", + "label": "BIOS", + "number": 1 + } + ] + } + ] + } + } + } +` + cacherPartitionSizeKB = ` + { + "id": "8978e7d4-1a55-4845-8a66-a5259236b104", + "instance": { + "storage": { + "disks": [ + { + "partitions": [ + { + "size": "24Kb", + "label": "BIOS", + "number": 1 + } + ] + } + ] + } + } + } +` + cacherPartitionSizeM = ` + { + "id": "8978e7d4-1a55-4845-8a66-a5259236b104", + "instance": { + "storage": { + "disks": [ + { + "partitions": [ + { + "size": "3m", + "label": "BIOS", + "number": 1 + } + ] + } + ] + } + } + } ` tinkerbellDataModel = ` { @@ -139,7 +260,61 @@ const ( "id":"", "slug":"" }, - "instance":{}, + "instance":{ + "storage": { + "disks": [ + { + "device": "/dev/sda", + "wipeTable": true, + "partitions": [ + { + "size": 4096, + "label": "BIOS", + "number": 1 + }, + { + "size": 3993600, + "label": "SWAP", + "number": 2 + }, + { + "size": 0, + "label": "ROOT", + "number": 3 + } + ] + } + ], + "filesystems": [ + { + "mount": { + "point": "/", + "create": { + "options": ["-L", "ROOT"] + }, + "device": "/dev/sda3", + "format": "ext4" + } + }, + { + "mount": { + "point": "none", + "create": { + "options": ["-L", "SWAP"] + }, + "device": "/dev/sda2", + "format": "swap" + } + } + ] + }, + "crypted_root_password": "$6$qViImWbWFfH/a4pq$s1bpFFXMpQj1eQbHWsruLy6/", + "operating_system_version": { + "distro": "ubuntu", + "version": "16.04", + "os_slug": "ubuntu_16_04" + } + }, "custom":{ "preinstalled_operating_system_version":{}, "private_subnets":[] diff --git a/grpc_server.go b/grpc_server.go index d53768ca..18cf4105 100644 --- a/grpc_server.go +++ b/grpc_server.go @@ -5,8 +5,12 @@ import ( "encoding/json" "errors" "io" + "math" "net" "os" + "strconv" + "strings" + "unicode" "github.com/packethost/cacher/protos/cacher" "github.com/packethost/hegel/grpc/hegel" @@ -25,8 +29,10 @@ type exportedHardware interface{} type exportedHardwareCacher struct { ID string `json:"id"` + Arch string `json:"arch"` State string `json:"state"` - Instance interface{} `json:"instance"` + EFIBoot bool `json:"efi_boot"` + Instance instance `json:"instance"` PreinstalledOperatingSystemVersion interface{} `json:"preinstalled_operating_system_version"` NetworkPorts []map[string]interface{} `json:"network_ports"` PlanSlug string `json:"plan_slug"` @@ -37,18 +43,129 @@ type exportedHardwareCacher struct { type exportedHardwareTinkerbell struct { ID string `json:"id"` - Metadata Metadata `json:"metadata"` + Metadata metadata `json:"metadata"` } -type Metadata struct { +type metadata struct { State string `json:"state"` BondingMode int `json:"bonding_mode"` Manufacturer interface{} `json:"manufacturer"` - Instance interface{} `json:"instance"` + Instance instance `json:"instance"` Custom interface{} `json:"custom"` Facility interface{} `json:"facility"` } +type instance struct { + ID string `json:"id"` + State string `json:"state"` + Hostname string `json:"hostname"` + AllowPXE bool `json:"allow_pxe"` + Rescue bool `json:"rescue"` + + OS operatingSystem `json:"operating_system_version"` + UserData string `json:"userdata,omitempty"` + + CryptedRootPassword string `json:"crypted_root_password,omitempty"` + + Storage storage `json:"storage,omitempty"` + SSHKeys []string `json:"ssh_keys,omitempty"` + NetworkReady bool `json:"network_ready,omitempty"` +} + +type operatingSystem struct { + Slug string `json:"slug"` + Distro string `json:"distro"` + Version string `json:"version"` + ImageTag string `json:"image_tag"` + OsSlug string `json:"os_slug"` +} + +type disk struct { + Device string `json:"device"` + WipeTable bool `json:"wipeTable,omitempty"` + Paritions []*partition `json:"partitions,omitempty"` +} + +type file struct { + Path string `json:"path"` + Contents string `json:"contents,omitempty"` + Mode int `json:"mode,omitempty"` + UID int `json:"uid,omitempty"` + GID int `json:"gid,omitempty"` +} + +type filesystem struct { + Mount struct { + Device string `json:"device"` + Format string `json:"format"` + Files []*file `json:"files,omitempty"` + Create *filesystemOptions `json:"create,omitempty"` + } `json:"mount"` +} + +type filesystemOptions struct { + Force bool `json:"force,omitempty"` + Options []string `json:"options,omitempty"` +} + +type partition struct { + Label string `json:"label,omitempty"` + Number int `json:"number,omitempty"` + Size intOrString `json:"size,omitempty"` + Start int `json:"start,omitempty"` + TypeGUID string `json:"typeGuid,omitempty"` +} + +type RAID struct { + Name string `json:"name"` + Level string `json:"level"` + Devices []string `json:"devices"` + Spares int `json:"spares,omitempty"` +} + +type storage struct { + Disks []*disk `json:"disks,omitempty"` + RAID []*RAID `json:"raid,omitempty"` + Filesystems []*filesystem `json:"filesystems,omitempty"` +} + +type intOrString int + +func (ios *intOrString) UnmarshalJSON(b []byte) error { + if b[0] != '"' { + return json.Unmarshal(b, (*int)(ios)) + } + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + i, err := convertSuffix(s) + if err != nil { + return err + } + *ios = intOrString(i) + return nil +} + +func convertSuffix(s string) (int, error) { + suffixes := map[string]int{"k": 1, "m": 2, "g": 3, "t": 4} + s = strings.ToLower(s) + i := strings.TrimFunc(s, func(r rune) bool { + return !unicode.IsNumber(r) + }) + size, err := strconv.Atoi(i) + if err != nil { + return 0, err + } + + suf := strings.TrimFunc(s, func(r rune) bool { + return !unicode.IsLetter(r) + }) + suf = strings.ReplaceAll(suf, "b", "") + res := size * int(math.Pow(1024, float64(suffixes[suf]))) + return res, nil +} + // exportedHardware transforms hardware that is returned from cacher/tink into what we want to expose to clients func exportHardware(hw []byte) ([]byte, error) { var exported exportedHardware @@ -87,7 +204,6 @@ func (eh *exportedHardwareCacher) UnmarshalJSON(b []byte) error { } func (s *server) Get(ctx context.Context, in *hegel.GetRequest) (*hegel.GetResponse, error) { - // todo add tink p, ok := peer.FromContext(ctx) if !ok { return nil, errors.New("could not get peer info from client") @@ -300,7 +416,7 @@ func getByIP(ctx context.Context, s *server, userIP string) ([]byte, error) { return nil, errors.New("could not find hardware") } - hw = []byte(resp.(*cacher.Hardware).JSON) + hw = []byte(resp.(cacher.Hardware).JSON) } ehw, err := exportHardware(hw) diff --git a/grpc_server_test.go b/grpc_server_test.go index 7aaf218d..5b2a27b0 100644 --- a/grpc_server_test.go +++ b/grpc_server_test.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "fmt" "os" "testing" @@ -10,19 +11,21 @@ import ( "google.golang.org/grpc" ) -type hardwareGetterMock struct{} +type hardwareGetterMock struct { + hardwareResp string +} func (hg hardwareGetterMock) ByIP(ctx context.Context, in getRequest, opts ...grpc.CallOption) (hardware, error) { var hw hardware dataModelVersion := os.Getenv("DATA_MODEL_VERSION") switch dataModelVersion { case "1": - err := json.Unmarshal([]byte(tinkerbellDataModel), &hw) + err := json.Unmarshal([]byte(hg.hardwareResp), &hw) if err != nil { return nil, err } default: - hw = cacher.Hardware{JSON: cacherDataModel} + hw = cacher.Hardware{JSON: hg.hardwareResp} } return hw, nil @@ -35,13 +38,14 @@ func (hg hardwareGetterMock) Watch(ctx context.Context, in getRequest, opts ...g func TestGetByIPCacher(t *testing.T) { t.Log("DATA_MODEL_VERSION (empty to use cacher):", os.Getenv("DATA_MODEL_VERSION")) - var hgm hardwareGetter = hardwareGetterMock{} - hegelTestServer := &server{ - log: logger, - hardwareClient: hgm, - } for name, test := range cacherGrpcTests { t.Log(name) + + var hgm hardwareGetter = hardwareGetterMock{test.json} + hegelTestServer := &server{ + log: logger, + hardwareClient: hgm, + } ehw, err := getByIP(context.Background(), hegelTestServer, test.remote) if err != nil { t.Fatal("Error in Finding Hardware", err) @@ -51,9 +55,60 @@ func TestGetByIPCacher(t *testing.T) { if err != nil { t.Fatal("Error in unmarshalling hardware", err) } + + //instance := instance{} + //err = json.Unmarshal(hw.instance, &instance) + //if err != nil { + // t.Fatal("Error in unmarshalling hardware instance", err) + //} + //fmt.Println(instance) + + if hw.State != test.state { + t.Fatalf("unexpected state, want: %v, got: %v\n", test.state, hw.State) + } + if hw.Facility != test.facility { + t.Fatalf("unexpected facility, want: %v, got: %v\n", test.facility, hw.Facility) + } + if len(hw.NetworkPorts) > 0 && hw.NetworkPorts[0]["data"].(map[string]interface{})["mac"] != test.mac { + t.Fatalf("unexpected mac, want: %v, got: %v\n", test.mac, hw.NetworkPorts[0]["data"]) + } + if len(hw.Instance.Storage.Disks) > 0 { + if hw.Instance.Storage.Disks[0].Device != test.diskDevice { + t.Fatalf("unexpected storage disk device, want: %v, got: %v\n", test.diskDevice, hw.Instance.Storage.Disks[0].Device) + } + if hw.Instance.Storage.Disks[0].WipeTable != test.wipeTable { + t.Fatalf("unexpected storage disk wipe table, want: %v, got: %v\n", test.wipeTable, hw.Instance.Storage.Disks[0].WipeTable) + } + if int(hw.Instance.Storage.Disks[0].Paritions[0].Size) != test.partionSize { + t.Fatalf("unexpected storage disk partition size, want: %v, got: %v\n", test.partionSize, hw.Instance.Storage.Disks[0].Paritions[0].Size) + } + } + if len(hw.Instance.Storage.Filesystems) > 0 { + if hw.Instance.Storage.Filesystems[0].Mount.Device != test.filesystemDevice { + t.Fatalf("unexpected storage filesystem mount device, want: %v, got: %v\n", test.filesystemDevice, hw.Instance.Storage.Filesystems[0].Mount.Device) + } + if hw.Instance.Storage.Filesystems[0].Mount.Format != test.filesystemFormat { + t.Fatalf("unexpected storage filesystem mount format, want: %v, got: %v\n", test.filesystemFormat, hw.Instance.Storage.Filesystems[0].Mount.Format) + } + } + if hw.Instance.OS.Slug != test.osSlug { + t.Fatalf("unexpected os slug, want: %v, got: %v\n", test.osSlug, hw.Instance.OS.Slug) + } if hw.PlanSlug != test.planSlug { t.Fatalf("unexpected plan slug, want: %v, got: %v\n", test.planSlug, hw.PlanSlug) } + + //fmt.Println(hw.State) + //fmt.Println(hw.Facility) + //fmt.Println(hw.Instance.Storage.Disks[0].Device) + //fmt.Println(hw.Instance.Storage.Disks[0].WipeTable) + fmt.Println(hw.Instance.Storage.Disks[0].Paritions[0].Size) + //fmt.Println(hw.Instance.Storage.Filesystems[0].Mount.Device) + //fmt.Println(hw.Instance.Storage.Filesystems[0].Mount.Format) + //fmt.Println(hw.Instance.OS.Slug) + + //instance := reflect.ValueOf(hw.instance) + //fmt.Println(instance.MapIndex(reflect.ValueOf("storage"))) } } @@ -61,14 +116,14 @@ func TestGetByIPTinkerbell(t *testing.T) { os.Setenv("DATA_MODEL_VERSION", "1") t.Log("DATA_MODEL_VERSION:", os.Getenv("DATA_MODEL_VERSION")) - var hgm hardwareGetter = hardwareGetterMock{} - hegelTestServer := &server{ - log: logger, - hardwareClient: hgm, - } - for name, test := range tinkerbellGrpcTests { t.Log(name) + + var hgm hardwareGetter = hardwareGetterMock{test.json} + hegelTestServer := &server{ + log: logger, + hardwareClient: hgm, + } ehw, err := getByIP(context.Background(), hegelTestServer, test.remote) if err != nil { t.Fatal("Error in Finding Hardware", err) @@ -81,29 +136,86 @@ func TestGetByIPTinkerbell(t *testing.T) { if hw.Metadata.BondingMode != test.bondingMode { t.Fatalf("unexpected primary data mac, want: %v, got: %v\n", test.bondingMode, hw.Metadata.BondingMode) } + // TODO (kdeng3849) fill out the rest } } -var tinkerbellGrpcTests = map[string]struct { - remote string - bondingMode int - json string +var cacherGrpcTests = map[string]struct { + remote string + state string + facility string + mac string + diskDevice string + wipeTable bool + partionSize int + filesystemDevice string + filesystemFormat string + osSlug string + planSlug string + json string }{ - "tinkerbell": { - remote: "192.168.1.5", - bondingMode: 5, - json: tinkerbellDataModel, + "cacher": { + remote: "192.168.1.5", + state: "provisioning", + facility: "onprem", + mac: "98:03:9b:48:de:bc", + diskDevice: "/dev/sda", + wipeTable: true, + partionSize: 4096, + filesystemDevice: "/dev/sda3", + filesystemFormat: "ext4", + planSlug: "t1.small.x86", + json: cacherDataModel, + }, + "cacher_partition_size_int": { + partionSize: 4096, + json: cacherPartitionSizeInt, + }, + "cacher_partition_size_string": { + partionSize: 3333, + json: cacherPartitionSizeString, + }, + "cacher_partition_size_whitespace": { + partionSize: 1234, + json: cacherPartitionSizeWhitespace, + }, + "cacher_partition_size_k": { + partionSize: 24576, + json: cacherPartitionSizeK, + }, + "cacher_partition_size_kb": { + partionSize: 24576, + json: cacherPartitionSizeKB, + }, + "cacher_partition_size_m": { + partionSize: 3145728, + json: cacherPartitionSizeM, }, } -var cacherGrpcTests = map[string]struct { - remote string - planSlug string - json string +var tinkerbellGrpcTests = map[string]struct { + remote string + state string + bondingMode int + diskDevice string + wipeTable bool + partionSize int + filesystemDevice string + filesystemFormat string + osSlug string + planSlug string + json string }{ - "cacher": { - remote: "192.168.1.5", - planSlug: "t1.small.x86", - json: cacherDataModel, + "tinkerbell": { + remote: "192.168.1.5", + bondingMode: 5, + diskDevice: "/dev/sda", + wipeTable: true, + partionSize: 4096, + filesystemDevice: "/dev/sda3", + filesystemFormat: "ext4", + osSlug: "ubuntu_16_04", + planSlug: "c2.medium.x86", + json: tinkerbellDataModel, }, }