From 77cf4177e3d367620730cff40d0b5666d86c5b8c Mon Sep 17 00:00:00 2001 From: Alistair Yan <52126234+1riatsila1@users.noreply.github.com> Date: Tue, 22 Oct 2024 13:12:56 +0100 Subject: [PATCH] feat: delete group and sensors get integration (#45) --- .../provider/data-sources/sensor.go | 163 +++++++++++++++++ pkg/config-api-provider/provider/provider.go | 1 + .../provider/resources/group.go | 10 ++ .../provider/resources/sensor.go | 69 +++++--- .../test/data-sources/sensor_test.go | 141 +++++++++++++++ .../resources/agent_group_assignment_test.go | 17 ++ .../test/resources/group_test.go | 62 ++++++- .../network_group_assignment_test.go | 6 + .../resources/sensor_group_assignment_test.go | 63 ++++--- .../test/resources/sensor_test.go | 166 +++++++++++++++++- .../service_test_group_assignment_test.go | 7 + pkg/config-api-provider/test/util/utils.go | 45 +++-- 12 files changed, 675 insertions(+), 75 deletions(-) create mode 100644 pkg/config-api-provider/provider/data-sources/sensor.go create mode 100644 pkg/config-api-provider/test/data-sources/sensor_test.go diff --git a/pkg/config-api-provider/provider/data-sources/sensor.go b/pkg/config-api-provider/provider/data-sources/sensor.go new file mode 100644 index 00000000..abe27413 --- /dev/null +++ b/pkg/config-api-provider/provider/data-sources/sensor.go @@ -0,0 +1,163 @@ +package datasources + +import ( + "context" + + config_api_client "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/config-api-client" + "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/provider/util" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var ( + _ datasource.DataSource = &sensorDataSource{} + _ datasource.DataSourceWithConfigure = &sensorDataSource{} +) + +func NewSensorDataSource() datasource.DataSource { + return &sensorDataSource{} +} + +type sensorDataSource struct { + client *config_api_client.APIClient +} + +type sensorDataSourceModel struct { + Id types.String `tfsdk:"id"` + Serial types.String `tfsdk:"serial"` + Name types.String `tfsdk:"name"` + ModelNumber types.String `tfsdk:"model_number"` + WifiMacAddress types.String `tfsdk:"wifi_mac_address"` + EthernetMacAddress types.String `tfsdk:"ethernet_mac_address"` + AddressNote types.String `tfsdk:"address_note"` + Longitude types.Float32 `tfsdk:"longitude"` + Latitude types.Float32 `tfsdk:"latitude"` + Notes types.String `tfsdk:"notes"` + PcapMode types.String `tfsdk:"pcap_mode"` + Filter struct { + SensorID types.String `tfsdk:"sensor_id"` + } `tfsdk:"filter"` +} + +func (d *sensorDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_sensor" +} + +func (d *sensorDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "serial": schema.StringAttribute{ + Computed: true, + }, + "name": schema.StringAttribute{ + Computed: true, + }, + "model_number": schema.StringAttribute{ + Computed: true, + }, + "wifi_mac_address": schema.StringAttribute{ + Computed: true, + }, + "ethernet_mac_address": schema.StringAttribute{ + Computed: true, + }, + "address_note": schema.StringAttribute{ + Computed: true, + }, + "longitude": schema.Float32Attribute{ + Computed: true, + }, + "latitude": schema.Float32Attribute{ + Computed: true, + }, + "notes": schema.StringAttribute{ + Computed: true, + }, + "pcap_mode": schema.StringAttribute{ + Computed: true, + }, + "filter": schema.SingleNestedAttribute{ + Required: true, + Attributes: map[string]schema.Attribute{ + "sensor_id": schema.StringAttribute{ + Required: true, + }, + }, + }, + }, + } +} + +func (d *sensorDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + var state sensorDataSourceModel + + // Read configuration from request + diags := req.Config.Get(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + request := d.client.ConfigurationAPI. + GetUxiV1alpha1SensorsGet(ctx). + Id(state.Filter.SensorID.ValueString()) + + sensorResponse, response, err := util.RetryFor429(request.Execute) + errorPresent, errorDetail := util.RaiseForStatus(response, err) + + errorSummary := util.GenerateErrorSummary("read", "uxi_sensor") + + if errorPresent { + resp.Diagnostics.AddError(errorSummary, errorDetail) + return + } + + if len(sensorResponse.Items) != 1 { + resp.Diagnostics.AddError(errorSummary, "Could not find specified data source") + return + } + + sensor := sensorResponse.Items[0] + + state.Id = types.StringValue(sensor.Id) + state.Name = types.StringValue(sensor.Name) + state.ModelNumber = types.StringPointerValue(sensor.ModelNumber.Get()) + state.WifiMacAddress = types.StringPointerValue(sensor.WifiMacAddress.Get()) + state.EthernetMacAddress = types.StringPointerValue(sensor.EthernetMacAddress.Get()) + state.AddressNote = types.StringPointerValue(sensor.AddressNote.Get()) + state.Longitude = types.Float32PointerValue(sensor.Longitude.Get()) + state.Latitude = types.Float32PointerValue(sensor.Latitude.Get()) + state.Notes = types.StringPointerValue(sensor.Notes.Get()) + state.PcapMode = types.StringPointerValue(sensor.PcapMode.Get()) + + // Set state + diags = resp.State.Set(ctx, &state) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } +} + +func (d *sensorDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Add a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*config_api_client.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + "Data Source type: Sensor. Please report this issue to the provider developers.", + ) + return + } + + d.client = client +} diff --git a/pkg/config-api-provider/provider/provider.go b/pkg/config-api-provider/provider/provider.go index 164cb376..a5f20b96 100644 --- a/pkg/config-api-provider/provider/provider.go +++ b/pkg/config-api-provider/provider/provider.go @@ -156,6 +156,7 @@ func (p *uxiConfigurationProvider) Configure(ctx context.Context, req provider.C func (p *uxiConfigurationProvider) DataSources(_ context.Context) []func() datasource.DataSource { return []func() datasource.DataSource{ datasources.NewGroupDataSource, + datasources.NewSensorDataSource, datasources.NewWiredNetworkDataSource, datasources.NewWirelessNetworkDataSource, datasources.NewSensorGroupAssignmentDataSource, diff --git a/pkg/config-api-provider/provider/resources/group.go b/pkg/config-api-provider/provider/resources/group.go index 9f1634ac..71aeefa4 100644 --- a/pkg/config-api-provider/provider/resources/group.go +++ b/pkg/config-api-provider/provider/resources/group.go @@ -205,6 +205,16 @@ func (r *groupResource) Delete(ctx context.Context, req resource.DeleteRequest, } // Delete existing group using the plan_id + request := r.client.ConfigurationAPI. + GroupsDeleteUxiV1alpha1GroupsGroupUidDelete(ctx, state.ID.ValueString()) + + _, response, err := util.RetryFor429(request.Execute) + errorPresent, errorDetail := util.RaiseForStatus(response, err) + + if errorPresent { + resp.Diagnostics.AddError(util.GenerateErrorSummary("delete", "uxi_group"), errorDetail) + return + } } func (r *groupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { diff --git a/pkg/config-api-provider/provider/resources/sensor.go b/pkg/config-api-provider/provider/resources/sensor.go index 0b1a940d..e804b234 100644 --- a/pkg/config-api-provider/provider/resources/sensor.go +++ b/pkg/config-api-provider/provider/resources/sensor.go @@ -2,8 +2,9 @@ package resources import ( "context" + "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/config-api-client" + "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/provider/util" - // "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/config-api-client" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource/schema" @@ -53,7 +54,9 @@ func NewSensorResource() resource.Resource { return &sensorResource{} } -type sensorResource struct{} +type sensorResource struct { + client *config_api_client.APIClient +} func (r *sensorResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_sensor" @@ -85,7 +88,23 @@ func (r *sensorResource) Schema(_ context.Context, _ resource.SchemaRequest, res } func (r *sensorResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Add a nil check when handling ProviderData because Terraform + // sets that data after it calls the ConfigureProvider RPC. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*config_api_client.APIClient) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + "Resource type: Group. Please report this issue to the provider developers.", + ) + return + } + r.client = client } func (r *sensorResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { @@ -105,13 +124,30 @@ func (r *sensorResource) Read(ctx context.Context, req resource.ReadRequest, res return } - response := GetSensor(state.ID.ValueString()) + request := r.client.ConfigurationAPI. + GetUxiV1alpha1SensorsGet(ctx). + Id(state.ID.ValueString()) + sensorResponse, response, err := util.RetryFor429(request.Execute) + errorPresent, errorDetail := util.RaiseForStatus(response, err) + + errorSummary := util.GenerateErrorSummary("read", "uxi_sensor") + + if errorPresent { + resp.Diagnostics.AddError(errorSummary, errorDetail) + return + } + + if len(sensorResponse.Items) != 1 { + resp.Diagnostics.AddError(errorSummary, "Could not find specified resource") + return + } + sensor := sensorResponse.Items[0] // Update state from client response - state.Name = types.StringValue(response.Name) - state.AddressNote = types.StringValue(response.AddressNote) - state.Notes = types.StringValue(response.Notes) - state.PCapMode = types.StringValue(response.PCapMode) + state.Name = types.StringValue(sensor.Name) + state.AddressNote = types.StringPointerValue(sensor.AddressNote.Get()) + state.Notes = types.StringPointerValue(sensor.Notes.Get()) + state.PCapMode = types.StringPointerValue(sensor.PcapMode.Get()) // Set refreshed state diags = resp.State.Set(ctx, &state) @@ -164,25 +200,6 @@ func (r *sensorResource) ImportState(ctx context.Context, req resource.ImportSta resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) } -// Get the sensor using the configuration-api client -var GetSensor = func(uid string) SensorResponseModel { - // TODO: Query the sensor using the client - - return SensorResponseModel{ - UID: uid, - Serial: "mock_serial", - Name: "mock_name", - ModelNumber: "mock_model_number", - WifiMacAddress: "mock_wifi_mac_address", - EthernetMacAddress: "mock_ethernet_mac_address", - AddressNote: "mock_address_note", - Longitude: "mock_longitude", - Latitude: "mock_latitude", - Notes: "mock_notes", - PCapMode: "mock_pcap_mode", - } -} - // Update the sensor using the configuration-api client var UpdateSensor = func(request SensorUpdateRequestModel) SensorResponseModel { // TODO: Query the sensor using the client diff --git a/pkg/config-api-provider/test/data-sources/sensor_test.go b/pkg/config-api-provider/test/data-sources/sensor_test.go new file mode 100644 index 00000000..43042262 --- /dev/null +++ b/pkg/config-api-provider/test/data-sources/sensor_test.go @@ -0,0 +1,141 @@ +package data_source_test + +import ( + "regexp" + "testing" + + "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/test/provider" + "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/test/util" + "github.com/h2non/gock" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/nbio/st" +) + +func TestSensorDataSource(t *testing.T) { + defer gock.Off() + mockOAuth := util.MockOAuth() + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: provider.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // Test Read + { + PreConfig: func() { + util.MockGetSensor( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateSensorResponseModel("uid", "")}), + 3, + ) + }, + Config: provider.ProviderConfig + ` + data "uxi_sensor" "my_sensor" { + filter = { + sensor_id = "uid" + } + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.uxi_sensor.my_sensor", "id", "uid"), + ), + }, + }, + }) + + mockOAuth.Mock.Disable() +} + +func TestSensorDataSource429Handling(t *testing.T) { + defer gock.Off() + mockOAuth := util.MockOAuth() + var mock429 *gock.Response + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: provider.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + + // Test Read + { + PreConfig: func() { + mock429 = gock.New("https://test.api.capenetworks.com"). + Get("/uxi/v1alpha1/sensors"). + Reply(429). + SetHeaders(util.RateLimitingHeaders) + util.MockGetSensor( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateSensorResponseModel("uid", "")}), + 3, + ) + }, + Config: provider.ProviderConfig + ` + data "uxi_sensor" "my_sensor" { + filter = { + sensor_id = "uid" + } + } + `, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.uxi_sensor.my_sensor", "id", "uid"), + func(s *terraform.State) error { + st.Assert(t, mock429.Mock.Request().Counter, 0) + return nil + }, + ), + }, + }, + }) + + mockOAuth.Mock.Disable() +} + +func TestSensorDataSourceHttpErrorHandling(t *testing.T) { + defer gock.Off() + mockOAuth := util.MockOAuth() + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: provider.TestAccProtoV6ProviderFactories, + Steps: []resource.TestStep{ + // 5xx error + { + PreConfig: func() { + gock.New("https://test.api.capenetworks.com"). + Get("/uxi/v1alpha1/sensors"). + Reply(500). + JSON(map[string]interface{}{ + "httpStatusCode": 500, + "errorCode": "HPE_GL_ERROR_INTERNAL_SERVER_ERROR", + "message": "Current request cannot be processed due to unknown issue", + "debugId": "12312-123123-123123-1231212", + }) + }, + Config: provider.ProviderConfig + ` + data "uxi_sensor" "my_sensor" { + filter = { + sensor_id = "uid" + } + } + `, + ExpectError: regexp.MustCompile(`(?s)Current request cannot be processed due to unknown issue\s*DebugID: 12312-123123-123123-1231212`), + }, + // Not found error + { + PreConfig: func() { + util.MockGetSensor( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{}), + 1, + ) + }, + Config: provider.ProviderConfig + ` + data "uxi_sensor" "my_sensor" { + filter = { + sensor_id = "uid" + } + } + `, + ExpectError: regexp.MustCompile(`Could not find specified data source`), + }, + }, + }) + + mockOAuth.Mock.Disable() +} diff --git a/pkg/config-api-provider/test/resources/agent_group_assignment_test.go b/pkg/config-api-provider/test/resources/agent_group_assignment_test.go index 7a7f3fd3..b4ac6f80 100644 --- a/pkg/config-api-provider/test/resources/agent_group_assignment_test.go +++ b/pkg/config-api-provider/test/resources/agent_group_assignment_test.go @@ -159,6 +159,23 @@ func TestAgentGroupAssignmentResource(t *testing.T) { ), }, // Delete testing automatically occurs in TestCase + { + PreConfig: func() { + util.MockGetGroup( + "group_uid", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateGroupResponseModel("group_uid", "", "")}), + 2, + ) + util.MockGetGroup( + "group_uid_2", + util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateGroupResponseModel("group_uid_2", "_2", "_2")}), + 1, + ) + util.MockDeleteGroup("group_uid", 1) + util.MockDeleteGroup("group_uid_2", 1) + }, + Config: provider.ProviderConfig, + }, }, }) diff --git a/pkg/config-api-provider/test/resources/group_test.go b/pkg/config-api-provider/test/resources/group_test.go index f61b71e4..9e95a320 100644 --- a/pkg/config-api-provider/test/resources/group_test.go +++ b/pkg/config-api-provider/test/resources/group_test.go @@ -98,6 +98,8 @@ func TestGroupResource(t *testing.T) { []map[string]interface{}{util.GenerateGroupResponseModel("new_uid", "", "_2")}), 1, ) + // delete old group (being replaced) + util.MockDeleteGroup("uid", 1) }, Config: provider.ProviderConfig + ` resource "uxi_group" "my_group" { @@ -110,7 +112,17 @@ func TestGroupResource(t *testing.T) { resource.TestCheckResourceAttr("uxi_group.my_group", "id", "new_uid"), ), }, - // Delete testing automatically occurs in TestCase + // Delete testing + { + PreConfig: func() { + util.MockGetGroup("new_uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateGroupResponseModel("new_uid", "", "_2")}), + 1, + ) + util.MockDeleteGroup("new_uid", 1) + }, + Config: provider.ProviderConfig, + }, }, }) @@ -227,7 +239,17 @@ func TestGroupResource429Handling(t *testing.T) { }, ), }, - // TODO: Test Deleting 429s + // Delete testing + { + PreConfig: func() { + util.MockGetGroup("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateGroupResponseModel("uid", "", "")}), + 1, + ) + util.MockDeleteGroup("uid", 1) + }, + Config: provider.ProviderConfig, + }, }, }) @@ -355,7 +377,41 @@ func TestGroupResourceHttpErrorHandling(t *testing.T) { }`, ExpectError: regexp.MustCompile(`(?s)Unable to create group - a sibling group already has the specified name\s*DebugID: 12312-123123-123123-1231212`), }, - // TODO: Test Deleting Error Handling + // Delete 4xx + { + PreConfig: func() { + // existing group + util.MockGetGroup("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateGroupResponseModel("uid", "", "")}), + 1, + ) + // delete group - with error + gock.New("https://test.api.capenetworks.com"). + Delete("/uxi/v1alpha1/groups/uid"). + Reply(422). + JSON(map[string]interface{}{ + "httpStatusCode": 422, + "errorCode": "HPE_GL_UXI_GROUP_CANNOT_BE_DELETED", + "message": "Unable to delete group", + "debugId": "12312-123123-123123-1231212", + }) + }, + Config: provider.ProviderConfig, + ExpectError: regexp.MustCompile(`(?s)Unable to delete group\s*DebugID: 12312-123123-123123-1231212`), + }, + // Actually delete group for cleanup reasons + { + PreConfig: func() { + // existing group + util.MockGetGroup("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateGroupResponseModel("uid", "", "")}), + 1, + ) + // delete group + util.MockDeleteGroup("uid", 1) + }, + Config: provider.ProviderConfig, + }, }, }) diff --git a/pkg/config-api-provider/test/resources/network_group_assignment_test.go b/pkg/config-api-provider/test/resources/network_group_assignment_test.go index adecd50f..82430724 100644 --- a/pkg/config-api-provider/test/resources/network_group_assignment_test.go +++ b/pkg/config-api-provider/test/resources/network_group_assignment_test.go @@ -206,6 +206,8 @@ func TestNetworkGroupAssignmentResourceForWiredNetwork(t *testing.T) { 1, ) + util.MockDeleteGroup("group_uid", 1) + util.MockDeleteGroup("group_uid_2", 1) util.MockDeleteNetworkGroupAssignment("network_group_assignment_uid", 1) util.MockDeleteNetworkGroupAssignment("network_group_assignment_uid_2", 1) }, @@ -430,6 +432,8 @@ func TestNetworkGroupAssignmentResourceForWirelessNetwork(t *testing.T) { 1, ) + util.MockDeleteGroup("group_uid", 1) + util.MockDeleteGroup("group_uid_2", 1) util.MockDeleteNetworkGroupAssignment("network_group_assignment_uid", 1) util.MockDeleteNetworkGroupAssignment("network_group_assignment_uid_2", 1) }, @@ -542,6 +546,7 @@ func TestNetworkGroupAssignmentResource429Handling(t *testing.T) { 1, ) + util.MockDeleteGroup("group_uid", 1) mock429 = gock.New("https://test.api.capenetworks.com"). Delete("/uxi/v1alpha1/network-group-assignments/network_group_assignment_uid"). Reply(429). @@ -839,6 +844,7 @@ func TestNetworkGroupAssignmentResourceHttpErrorHandling(t *testing.T) { util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateNetworkGroupAssignmentResponse("network_group_assignment_uid", "")}), 1, ) + util.MockDeleteGroup("group_uid", 1) util.MockDeleteNetworkGroupAssignment("network_group_assignment_uid", 1) }, Config: provider.ProviderConfig + ` diff --git a/pkg/config-api-provider/test/resources/sensor_group_assignment_test.go b/pkg/config-api-provider/test/resources/sensor_group_assignment_test.go index 663b6af7..001999ba 100644 --- a/pkg/config-api-provider/test/resources/sensor_group_assignment_test.go +++ b/pkg/config-api-provider/test/resources/sensor_group_assignment_test.go @@ -4,7 +4,6 @@ import ( "regexp" "testing" - "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/provider/resources" "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/test/provider" "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/test/util" "github.com/h2non/gock" @@ -24,9 +23,10 @@ func TestSensorGroupAssignmentResource(t *testing.T) { { PreConfig: func() { // required for sensor import - resources.GetSensor = func(uid string) resources.SensorResponseModel { - return util.GenerateSensorResponseModel(uid, "") - } + util.MockGetSensor("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("sensor_uid", "")}), + 2, + ) // required for group create util.MockPostGroup(util.StructToMap(util.GenerateGroupResponseModel("group_uid", "", "")), 1) @@ -93,13 +93,15 @@ func TestSensorGroupAssignmentResource(t *testing.T) { // Update and Read testing { PreConfig: func() { - resources.GetSensor = func(uid string) resources.SensorResponseModel { - if uid == "sensor_uid" { - return util.GenerateSensorResponseModel("sensor_uid", "") - } else { - return util.GenerateSensorResponseModel("sensor_uid", "_2") - } - } + util.MockGetSensor("sensor_uid_2", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("sensor_uid_2", "_2")}), + 2, + ) + util.MockGetSensor("sensor_uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("sensor_uid", "")}), + 3, + ) + util.MockGetGroup( "group_uid_2", util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateGroupResponseModel("group_uid_2", "_2", "_2")}), @@ -200,6 +202,8 @@ func TestSensorGroupAssignmentResource(t *testing.T) { 1, ) + util.MockDeleteGroup("group_uid", 1) + util.MockDeleteGroup("group_uid_2", 1) util.MockDeleteSensorGroupAssignment("sensor_group_assignment_uid_2", 1) }, Config: provider.ProviderConfig + ` @@ -237,9 +241,10 @@ func TestSensorGroupAssignmentResource429Handling(t *testing.T) { { PreConfig: func() { // required for sensor import - resources.GetSensor = func(uid string) resources.SensorResponseModel { - return util.GenerateSensorResponseModel(uid, "") - } + util.MockGetSensor("sensor_uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("sensor_uid", "")}), + 2, + ) // required for group create util.MockPostGroup(util.StructToMap(util.GenerateGroupResponseModel("group_uid", "", "")), 1) @@ -310,6 +315,7 @@ func TestSensorGroupAssignmentResource429Handling(t *testing.T) { 1, ) + util.MockDeleteGroup("group_uid", 1) mock429 = gock.New("https://test.api.capenetworks.com"). Delete("/uxi/v1alpha1/sensor-group-assignments/sensor_group_assignment_uid"). Reply(429). @@ -356,9 +362,10 @@ func TestSensorGroupAssignmentResourceHttpErrorHandling(t *testing.T) { { PreConfig: func() { // required for sensor import - resources.GetSensor = func(uid string) resources.SensorResponseModel { - return util.GenerateSensorResponseModel(uid, "") - } + util.MockGetSensor("sensor_uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("sensor_uid", "")}), + 1, + ) // required for group create util.MockPostGroup(util.StructToMap(util.GenerateGroupResponseModel("group_uid", "", "")), 1) @@ -408,9 +415,10 @@ func TestSensorGroupAssignmentResourceHttpErrorHandling(t *testing.T) { { PreConfig: func() { // required for sensor import - resources.GetSensor = func(uid string) resources.SensorResponseModel { - return util.GenerateSensorResponseModel(uid, "") - } + util.MockGetSensor("sensor_uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("sensor_uid", "")}), + 1, + ) // required for group create util.MockPostGroup(util.StructToMap(util.GenerateGroupResponseModel("group_uid", "", "")), 1) @@ -461,9 +469,10 @@ func TestSensorGroupAssignmentResourceHttpErrorHandling(t *testing.T) { { PreConfig: func() { // required for sensor import - resources.GetSensor = func(uid string) resources.SensorResponseModel { - return util.GenerateSensorResponseModel(uid, "") - } + util.MockGetSensor("sensor_uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("sensor_uid", "")}), + 1, + ) // required for group create util.MockPostGroup(util.StructToMap(util.GenerateGroupResponseModel("group_uid", "", "")), 1) @@ -519,9 +528,10 @@ func TestSensorGroupAssignmentResourceHttpErrorHandling(t *testing.T) { { PreConfig: func() { // required for sensor import - resources.GetSensor = func(uid string) resources.SensorResponseModel { - return util.GenerateSensorResponseModel(uid, "") - } + util.MockGetSensor("sensor_uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("sensor_uid", "")}), + 2, + ) // required for group create util.MockPostGroup(util.StructToMap(util.GenerateGroupResponseModel("group_uid", "", "")), 1) @@ -625,6 +635,7 @@ func TestSensorGroupAssignmentResourceHttpErrorHandling(t *testing.T) { util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateSensorGroupAssignmentResponse("sensor_group_assignment_uid", "")}), 1, ) + util.MockDeleteGroup("group_uid", 1) util.MockDeleteSensorGroupAssignment("sensor_group_assignment_uid", 1) }, Config: provider.ProviderConfig + ` diff --git a/pkg/config-api-provider/test/resources/sensor_test.go b/pkg/config-api-provider/test/resources/sensor_test.go index 00d67d2e..59bbadae 100644 --- a/pkg/config-api-provider/test/resources/sensor_test.go +++ b/pkg/config-api-provider/test/resources/sensor_test.go @@ -6,10 +6,11 @@ import ( "regexp" "testing" - "github.com/aruba-uxi/configuration-api-terraform-provider/pkg/terraform-provider-configuration/provider/resources" "github.com/h2non/gock" "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/nbio/st" ) func TestSensorResource(t *testing.T) { @@ -38,9 +39,10 @@ func TestSensorResource(t *testing.T) { // Importing a sensor { PreConfig: func() { - resources.GetSensor = func(uid string) resources.SensorResponseModel { - return util.GenerateSensorResponseModel(uid, "") - } + util.MockGetSensor("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("uid", "")}), + 2, + ) }, Config: provider.ProviderConfig + ` resource "uxi_sensor" "my_sensor" { @@ -65,6 +67,12 @@ func TestSensorResource(t *testing.T) { }, // ImportState testing { + PreConfig: func() { + util.MockGetSensor("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("uid", "")}), + 1, + ) + }, ResourceName: "uxi_sensor.my_sensor", ImportState: true, ImportStateVerify: true, @@ -72,9 +80,16 @@ func TestSensorResource(t *testing.T) { // Update and Read testing { PreConfig: func() { - resources.GetSensor = func(uid string) resources.SensorResponseModel { - return util.GenerateSensorResponseModel(uid, "_2") - } + // existing sensor + util.MockGetSensor("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("uid", "")}), + 1, + ) + // updated sensor + util.MockGetSensor("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("uid", "_2")}), + 1, + ) }, Config: provider.ProviderConfig + ` resource "uxi_sensor" "my_sensor" { @@ -93,6 +108,12 @@ func TestSensorResource(t *testing.T) { }, // Deleting a sensor is not allowed { + PreConfig: func() { + util.MockGetSensor("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("uid", "_2")}), + 1, + ) + }, Config: provider.ProviderConfig + ``, ExpectError: regexp.MustCompile(`deleting a sensor is not supported; sensors can only removed from state`), }, @@ -112,3 +133,134 @@ func TestSensorResource(t *testing.T) { mockOAuth.Mock.Disable() } + +func TestSensorResource429Handling(t *testing.T) { + defer gock.Off() + mockOAuth := util.MockOAuth() + var request429 *gock.Response + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: provider.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // we required terraform 1.7.0 and above for the `removed` block + tfversion.RequireAbove(tfversion.Version1_7_0), + }, + Steps: []resource.TestStep{ + // Importing a sensor + { + PreConfig: func() { + request429 = gock.New("https://test.api.capenetworks.com"). + Get("/uxi/v1alpha1/sensors"). + Reply(429). + SetHeaders(util.RateLimitingHeaders) + util.MockGetSensor("uid", util.GeneratePaginatedResponse( + []map[string]interface{}{util.GenerateSensorResponseModel("uid", "")}), + 2, + ) + }, + Config: provider.ProviderConfig + ` + resource "uxi_sensor" "my_sensor" { + name = "name" + address_note = "address_note" + notes = "notes" + pcap_mode = "light" + } + + import { + to = uxi_sensor.my_sensor + id = "uid" + }`, + + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("uxi_sensor.my_sensor", "id", "uid"), + func(s *terraform.State) error { + st.Assert(t, request429.Mock.Request().Counter, 0) + return nil + }, + ), + }, + // Remove sensor from state + { + Config: provider.ProviderConfig + ` + removed { + from = uxi_sensor.my_sensor + + lifecycle { + destroy = false + } + }`, + }, + }, + }) + + mockOAuth.Mock.Disable() + +} +func TestSensorResourceHttpErrorHandling(t *testing.T) { + defer gock.Off() + mockOAuth := util.MockOAuth() + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: provider.TestAccProtoV6ProviderFactories, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + // we required terraform 1.7.0 and above for the `removed` block + tfversion.RequireAbove(tfversion.Version1_7_0), + }, + Steps: []resource.TestStep{ + // Read 5xx error + { + PreConfig: func() { + gock.New("https://test.api.capenetworks.com"). + Get("/uxi/v1alpha1/sensors"). + Reply(500). + JSON(map[string]interface{}{ + "httpStatusCode": 500, + "errorCode": "HPE_GL_ERROR_INTERNAL_SERVER_ERROR", + "message": "Current request cannot be processed due to unknown issue", + "debugId": "12312-123123-123123-1231212", + }) + }, + Config: provider.ProviderConfig + ` + resource "uxi_sensor" "my_sensor" { + name = "name" + address_note = "address_note" + notes = "notes" + pcap_mode = "light" + } + + import { + to = uxi_sensor.my_sensor + id = "uid" + }`, + + ExpectError: regexp.MustCompile(`(?s)Current request cannot be processed due to unknown issue\s*DebugID: 12312-123123-123123-1231212`), + }, + // Read not found + { + PreConfig: func() { + util.MockGetSensor( + "uid", + util.GeneratePaginatedResponse([]map[string]interface{}{}), + 1, + ) + }, + Config: provider.ProviderConfig + ` + resource "uxi_sensor" "my_sensor" { + name = "name" + address_note = "address_note" + notes = "notes" + pcap_mode = "light" + } + + import { + to = uxi_sensor.my_sensor + id = "uid" + }`, + + ExpectError: regexp.MustCompile(`Could not find specified resource`), + }, + }, + }) + + mockOAuth.Mock.Disable() +} diff --git a/pkg/config-api-provider/test/resources/service_test_group_assignment_test.go b/pkg/config-api-provider/test/resources/service_test_group_assignment_test.go index 8e8f6703..7dd19727 100644 --- a/pkg/config-api-provider/test/resources/service_test_group_assignment_test.go +++ b/pkg/config-api-provider/test/resources/service_test_group_assignment_test.go @@ -173,6 +173,9 @@ func TestServiceTestGroupAssignmentResource(t *testing.T) { util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateGroupResponseModel("group_uid_2", "_2", "_2")}), 1, ) + + util.MockDeleteGroup("group_uid", 1) + util.MockDeleteGroup("group_uid_2", 1) }, Config: provider.ProviderConfig + ` removed { @@ -273,6 +276,8 @@ func TestServiceTestGroupAssignmentResource429Handling(t *testing.T) { util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateGroupResponseModel("group_uid", "", "")}), 1, ) + + util.MockDeleteGroup("group_uid", 1) }, Config: provider.ProviderConfig + ` removed { @@ -353,6 +358,8 @@ func TestServiceTestGroupAssignmentResourceHttpErrorHandling(t *testing.T) { util.GeneratePaginatedResponse([]map[string]interface{}{util.GenerateGroupResponseModel("group_uid", "", "")}), 1, ) + + util.MockDeleteGroup("group_uid", 1) }, Config: provider.ProviderConfig + ` removed { diff --git a/pkg/config-api-provider/test/util/utils.go b/pkg/config-api-provider/test/util/utils.go index e95e40f5..a022a0dc 100644 --- a/pkg/config-api-provider/test/util/utils.go +++ b/pkg/config-api-provider/test/util/utils.go @@ -8,19 +8,20 @@ import ( "github.com/h2non/gock" ) -func GenerateSensorResponseModel(uid string, postfix string) resources.SensorResponseModel { - return resources.SensorResponseModel{ - UID: uid, - Serial: "serial" + postfix, - Name: "name" + postfix, - ModelNumber: "model_number" + postfix, - WifiMacAddress: "wifi_mac_address" + postfix, - EthernetMacAddress: "ethernet_mac_address" + postfix, - AddressNote: "address_note" + postfix, - Longitude: "longitude" + postfix, - Latitude: "latitude" + postfix, - Notes: "notes" + postfix, - PCapMode: "light" + postfix, +func GenerateSensorResponseModel(uid string, postfix string) map[string]interface{} { + return map[string]interface{}{ + "id": uid, + "serial": "serial" + postfix, + "name": "name" + postfix, + "modelNumber": "model_number" + postfix, + "wifiMacAddress": "wifi_mac_address" + postfix, + "ethernetMacAddress": "ethernet_mac_address" + postfix, + "addressNote": "address_note" + postfix, + "longitude": 0.0, + "latitude": 0.0, + "notes": "notes" + postfix, + "pcapMode": "light" + postfix, + "type": "uxi/sensor", } } @@ -193,6 +194,24 @@ func MockUpdateGroup(uid string, response map[string]interface{}, times int) { JSON(response) } +func MockDeleteGroup(uid string, times int) { + gock.New("https://test.api.capenetworks.com"). + Delete("/uxi/v1alpha1/groups/"+uid). + MatchHeader("Authorization", "mock_token"). + Times(times). + Reply(204) +} + +func MockGetSensor(uid string, response map[string]interface{}, times int) { + gock.New("https://test.api.capenetworks.com"). + Get("/uxi/v1alpha1/sensors"). + MatchHeader("Authorization", "mock_token"). + MatchParam("id", uid). + Times(times). + Reply(200). + JSON(response) +} + func MockGetWiredNetwork(uid string, response map[string]interface{}, times int) { gock.New("https://test.api.capenetworks.com"). Get("/uxi/v1alpha1/wired-networks").