Skip to content

Commit 93c2f2f

Browse files
authored
Storage controller support (ddcloud_storage_controller) (#86)
* Expose SCSI bus number for inline disks on ddcloud_server. * If both IPv4 and VLAN Id are specified for a network adapter, ignore VLAN Id. * Handle the fact that ExpandDisk is part of the v2.x API and therefore returns ResponseCodeInProgress rather than ResultSuccess. * Only build master and development/vXXX. * Initial sketch of implementation for ddcloud_storage_controller (this is a prototype of option 1 for #84). * Mark all storage controller properties as ForceNew. * Start implementing update / delete for ddcloud_storage_controller resource. * Fix bugs and improve diagnostic logging for deployment of ddcloud_storage_controller. * Fix incorrectly-propagated scsi_bus_number on ddcloud_storage_controller.disk. * Log server remaining disk count before removing a disk. * Refactor access to target storage controller. * Create initial acceptance test for ddcloud_storage_controller (default controller, single image disk). * Fix broken cleanup checks for ddcloud_storage_controller acceptance tests. * Add acceptance test for ddcloud_storage_controller (default controller, 1 additional disk). * Remove SCSI controller when ddcloud_storage_controller is destroyed (unless it's the last storage controller in the target server). * Fix buggy handling for removal of server's last disk. * Add refresh step before verifying server disk configuration in acceptance tests for ddcloud_storage_controller. * Fix broken ddcloud_storage_controller acceptance test (default controller with 1 image disk and 1 additional disk). * Fix another broken acceptance test for ddcloud_storage_controller, and improve output from testCheckXXX functions. * Add docs for ddcloud_storage_controller. * Update version info and change log (v1.3.0-alpha2). * Fix failing unit tests due to CloudControl schema change. * Add ddcloud_storage_controller to provider documentation
1 parent fe91d4e commit 93c2f2f

18 files changed

+1864
-149
lines changed

.travis.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,11 @@ language: go
33
go:
44
- 1.8
55

6+
branches:
7+
only:
8+
- master
9+
- /^development\/v.*$/
10+
611
install:
712
- go get github.com/hashicorp/terraform
813
- pushd $GOPATH/src/github.com/hashicorp/terraform

CHANGES.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
# Changes
22

3+
# v1.3.0-alpha2
4+
5+
* Support for multiple storage controllers (using `ddcloud_storage_controller` resource).
6+
37
# v1.3.0-alpha1
48

59
* Support for additional disk storage tier (`ECONOMY`) and network adapter types (`E1000E`, `ENHANCED_VMXNET2`, and `FLEXIBLE_PCNET32`).

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
PROVIDER_NAME = ddcloud
22

3-
VERSION = 1.3.0-alpha1
3+
VERSION = 1.3.0-alpha2
44
VERSION_INFO_FILE = ./$(PROVIDER_NAME)/version-info.go
55

66
BIN_DIRECTORY = _bin

ddcloud/provider.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ func Provider() terraform.ResourceProvider {
7878
// A server (virtual machine).
7979
"ddcloud_server": resourceServer(),
8080

81+
// A storage controller (e.g. SCSI controller) in a server
82+
"ddcloud_storage_controller": resourceStorageController(),
83+
8184
// A network adapter.
8285
"ddcloud_network_adapter": resourceNetworkAdapter(),
8386

ddcloud/resource_network_adapter.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -380,8 +380,8 @@ func validateNetworkAdapterAdapterType(value interface{}, propertyName string) (
380380
switch adapterType {
381381
case compute.NetworkAdapterTypeE1000:
382382
case compute.NetworkAdapterTypeE1000E:
383-
case compute.NetworkAdapterTypeENHANCED_VMXNET2:
384-
case compute.NetworkAdapterTypeFLEXIBLE_PCNET32:
383+
case compute.NetworkAdapterTypeEnhancedVMXNET2:
384+
case compute.NetworkAdapterTypeFlexiblePCNET32:
385385
case compute.NetworkAdapterTypeVMXNET3:
386386
break
387387
default:

ddcloud/resource_server.go

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -305,19 +305,22 @@ func resourceServerCreate(data *schema.ResourceData, provider interface{}) error
305305

306306
// Validate disk configuration.
307307
configuredDisks := propertyHelper.GetDisks()
308-
err = validateDisks(configuredDisks)
308+
err = validateServerDisks(configuredDisks)
309309
if err != nil {
310310
return err
311311
}
312312

313313
// Image disk speeds
314-
configuredDisksByUnitID := configuredDisks.ByUnitID()
315-
for index := range deploymentConfiguration.Disks {
316-
deploymentDisk := &deploymentConfiguration.Disks[index]
317-
318-
configuredDisk, ok := configuredDisksByUnitID[deploymentDisk.SCSIUnitID]
319-
if ok {
320-
deploymentDisk.Speed = configuredDisk.Speed
314+
configuredDisksBySCSIPath := configuredDisks.BySCSIPath()
315+
for controllerIndex := range deploymentConfiguration.SCSIControllers {
316+
deploymentSCSIController := &deploymentConfiguration.SCSIControllers[controllerIndex]
317+
for diskIndex := range deploymentSCSIController.Disks {
318+
deploymentDisk := &deploymentSCSIController.Disks[diskIndex]
319+
320+
configuredDisk, ok := configuredDisksBySCSIPath[models.SCSIPath(deploymentSCSIController.BusNumber, deploymentDisk.SCSIUnitID)]
321+
if ok {
322+
deploymentDisk.Speed = configuredDisk.Speed
323+
}
321324
}
322325
}
323326

@@ -419,7 +422,7 @@ func resourceServerCreate(data *schema.ResourceData, provider interface{}) error
419422
}
420423
data.SetPartial(resourceKeyServerTag)
421424

422-
err = createDisks(server.Disks, data, providerState)
425+
err = createDisks(server.SCSIControllers, data, providerState)
423426
if err != nil {
424427
return err
425428
}
@@ -483,7 +486,7 @@ func resourceServerRead(data *schema.ResourceData, provider interface{}) error {
483486
}
484487

485488
propertyHelper.SetDisks(
486-
models.NewDisksFromVirtualMachineDisks(server.Disks),
489+
models.NewDisksFromVirtualMachineSCSIControllers(server.SCSIControllers),
487490
)
488491

489492
return nil

ddcloud/resource_server_disks.go

Lines changed: 79 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,12 @@ import (
1111
)
1212

1313
const (
14-
resourceKeyServerDisk = "disk"
15-
resourceKeyServerDiskID = "id"
16-
resourceKeyServerDiskUnitID = "scsi_unit_id"
17-
resourceKeyServerDiskSizeGB = "size_gb"
18-
resourceKeyServerDiskSpeed = "speed"
14+
resourceKeyServerDisk = "disk"
15+
resourceKeyServerDiskID = "id"
16+
resourceKeyServerDiskBusNumber = "scsi_bus_number"
17+
resourceKeyServerDiskUnitID = "scsi_unit_id"
18+
resourceKeyServerDiskSizeGB = "size_gb"
19+
resourceKeyServerDiskSpeed = "speed"
1920
)
2021

2122
func schemaDisk() *schema.Schema {
@@ -24,14 +25,19 @@ func schemaDisk() *schema.Schema {
2425
Optional: true,
2526
Computed: true,
2627
Default: nil,
27-
Description: "The set of virtual disks attached to the server",
28+
Description: "The set of virtual disks attached to a server or storage controller",
2829
Elem: &schema.Resource{
2930
Schema: map[string]*schema.Schema{
3031
resourceKeyServerDiskID: &schema.Schema{
3132
Type: schema.TypeString,
3233
Computed: true,
3334
Description: "The CloudControl identifier for the virtual disk (computed when the disk is first created)",
3435
},
36+
resourceKeyServerDiskBusNumber: &schema.Schema{
37+
Type: schema.TypeInt,
38+
Computed: true,
39+
Description: "The SCSI bus number for the disk",
40+
},
3541
resourceKeyServerDiskUnitID: &schema.Schema{
3642
Type: schema.TypeInt,
3743
Required: true,
@@ -56,8 +62,9 @@ func schemaDisk() *schema.Schema {
5662
}
5763

5864
// When creating a server resource, synchronise the server's disks with its resource data.
59-
// imageDisks refers to the newly-deployed server's collection of disks (i.e. image disks).
60-
func createDisks(imageDisks []compute.VirtualMachineDisk, data *schema.ResourceData, providerState *providerState) error {
65+
//
66+
// imageSCSIControllers refers to the newly-deployed server's collection of SCSI controllers and their associated disks.
67+
func createDisks(imageSCSIControllers compute.VirtualMachineSCSIControllers, data *schema.ResourceData, providerState *providerState) error {
6168
propertyHelper := propertyHelper(data)
6269
serverID := data.Id()
6370

@@ -66,16 +73,17 @@ func createDisks(imageDisks []compute.VirtualMachineDisk, data *schema.ResourceD
6673
// Since this is the first time, populate image disks.
6774
configuredDisks := propertyHelper.GetDisks()
6875
log.Printf("Configuration for server '%s' specifies %d disks: %#v.", serverID, len(configuredDisks), configuredDisks)
69-
actualDisks := models.NewDisksFromVirtualMachineDisks(imageDisks)
76+
actualDisks := models.NewDisksFromVirtualMachineSCSIControllers(imageSCSIControllers)
7077
configuredDisks.CaptureIDs(actualDisks)
7178

72-
err := validateDisks(configuredDisks)
79+
err := validateServerDisks(configuredDisks)
7380
if err != nil {
7481
return err
7582
}
7683

77-
log.Printf("Configuration for server '%s' specifies %d disks: %#v.", serverID, len(configuredDisks), configuredDisks)
7884
if len(configuredDisks) == 0 {
85+
log.Printf("Configuration for server '%s' does not specify any disks; the provider will assume all disks are either image-only, or configured via ddcloud_storage_controller.", serverID)
86+
7987
propertyHelper.SetDisks(actualDisks)
8088
propertyHelper.SetPartial(resourceKeyServerDisk)
8189

@@ -84,6 +92,8 @@ func createDisks(imageDisks []compute.VirtualMachineDisk, data *schema.ResourceD
8492
return nil
8593
}
8694

95+
log.Printf("Configuration for server '%s' specifies %d disks: %#v.", serverID, len(configuredDisks), configuredDisks)
96+
8797
propertyHelper.SetDisks(configuredDisks)
8898
propertyHelper.SetPartial(resourceKeyServerDisk)
8999

@@ -99,7 +109,7 @@ func createDisks(imageDisks []compute.VirtualMachineDisk, data *schema.ResourceD
99109

100110
// After initial server deployment, we only need to handle disks that were part of the original server image (and of those, only ones we need to modify after the initial deployment completed deployment).
101111
log.Printf("Configure image disks for server '%s'...", serverID)
102-
actualDisks = models.NewDisksFromVirtualMachineDisks(server.Disks)
112+
actualDisks = models.NewDisksFromVirtualMachineSCSIControllers(server.SCSIControllers)
103113
addDisks, modifyDisks, _ := configuredDisks.SplitByAction(actualDisks) // Ignore removeDisks since not all disks have been created yet
104114
if addDisks.IsEmpty() && modifyDisks.IsEmpty() {
105115
log.Printf("No post-deploy changes required for disks of server '%s'.", serverID)
@@ -138,24 +148,44 @@ func updateDisks(data *schema.ResourceData, providerState *providerState) error
138148

139149
return fmt.Errorf("server '%s' has been deleted", serverID)
140150
}
141-
actualDisks := models.NewDisksFromVirtualMachineDisks(server.Disks)
151+
actualDisks := models.NewDisksFromVirtualMachineSCSIControllers(server.SCSIControllers)
142152

143153
configuredDisks := propertyHelper.GetDisks()
144154
log.Printf("Configuration for server '%s' specifies %d disks: %#v.", serverID, len(configuredDisks), configuredDisks)
145155

146-
err = validateDisks(configuredDisks)
156+
// Detect switch to / from inline disks.
157+
previouslyConfiguredDisks := propertyHelper.GetOldDisks()
158+
if previouslyConfiguredDisks.IsEmpty() && !configuredDisks.IsEmpty() {
159+
// Switched to inline declaration of server disks.
160+
log.Printf("Disks for server '%s' are now configured inline, but were previously unspecified or configured via one or more ddcloud_storage_controllers.",
161+
serverID,
162+
)
163+
164+
// Can only switch to inline disks if there is a single (default) SCSI controller.
165+
if len(server.SCSIControllers) > 1 {
166+
return fmt.Errorf("server '%s' has multiple SCSI controllers, so disks cannot be configured inline",
167+
serverID,
168+
)
169+
}
170+
} else if !previouslyConfiguredDisks.IsEmpty() && configuredDisks.IsEmpty() {
171+
// Switched to external declaration of server disks.
172+
173+
log.Printf("Disks for server '%s' are now unspecified or configured via one or more ddcloud_storage_controllers, but were previously configured inline.",
174+
serverID,
175+
)
176+
}
177+
178+
err = validateServerDisks(configuredDisks)
147179
if err != nil {
148180
return err
149181
}
150182

151183
if configuredDisks.IsEmpty() {
152184
// No explicitly-configured disks.
153-
propertyHelper.SetDisks(
154-
models.NewDisksFromVirtualMachineDisks(server.Disks),
155-
)
185+
propertyHelper.SetDisks(actualDisks)
156186
propertyHelper.SetPartial(resourceKeyServerDisk)
157187

158-
log.Printf("Server '%s' now has %d disks: %#v.", serverID, len(server.Disks), server.Disks)
188+
log.Printf("Server '%s' now has %d disks: %#v.", serverID, server.SCSIControllers.GetDiskCount(), server.SCSIControllers)
159189

160190
return nil
161191
}
@@ -243,11 +273,11 @@ func processAddDisks(addDisks models.Disks, data *schema.ResourceData, providerS
243273

244274
server := resource.(*compute.Server)
245275
propertyHelper.SetDisks(
246-
models.NewDisksFromVirtualMachineDisks(server.Disks),
276+
models.NewDisksFromVirtualMachineSCSIControllers(server.SCSIControllers),
247277
)
248278
propertyHelper.SetPartial(resourceKeyServerDisk)
249279

250-
log.Printf("Server '%s' now has %d disks: %#v.", serverID, len(server.Disks), server.Disks)
280+
log.Printf("Server '%s' now has %d disks: %#v.", serverID, server.SCSIControllers.GetDiskCount(), server.SCSIControllers)
251281

252282
log.Printf("Added disk '%s' with SCSI unit ID %d to server '%s'.",
253283
addDisk.ID,
@@ -279,13 +309,13 @@ func processModifyDisks(modifyDisks models.Disks, data *schema.ResourceData, pro
279309

280310
return fmt.Errorf("server '%s' has been deleted", serverID)
281311
}
282-
actualDisks := models.NewDisksFromVirtualMachineDisks(server.Disks)
283-
actualDisksByUnitID := actualDisks.ByUnitID()
312+
actualDisks := models.NewDisksFromVirtualMachineSCSIControllers(server.SCSIControllers)
313+
actualDisksBySCSIPath := actualDisks.BySCSIPath()
284314

285315
for index := range modifyDisks {
286316
modifyDisk := &modifyDisks[index]
287317
log.Printf("modifyDisk = %#v", modifyDisk)
288-
actualImageDisk := actualDisksByUnitID[modifyDisk.SCSIUnitID]
318+
actualImageDisk := actualDisksBySCSIPath[modifyDisk.SCSIPath()]
289319

290320
// Can't shrink disk, only grow it.
291321
if modifyDisk.SizeGB < actualImageDisk.SizeGB {
@@ -313,16 +343,15 @@ func processModifyDisks(modifyDisks models.Disks, data *schema.ResourceData, pro
313343
asyncLock := providerState.AcquireAsyncOperationLock(operationDescription)
314344
defer asyncLock.Release()
315345

316-
response, resizeError := apiClient.ResizeServerDisk(serverID, modifyDisk.ID, modifyDisk.SizeGB)
346+
response, resizeError := apiClient.ExpandDisk(modifyDisk.ID, modifyDisk.SizeGB)
317347
if compute.IsResourceBusyError(resizeError) {
318348
context.Retry()
319349
} else if resizeError != nil {
320350
context.Fail(resizeError)
321351
}
322-
if response.Result != compute.ResultSuccess {
323-
context.Fail(response.ToError(
324-
"Unexpected result '%s' when resizing server disk '%s' for server '%s'.",
325-
response.Result,
352+
if response.ResponseCode != compute.ResponseCodeInProgress {
353+
context.Fail(response.ToError("unexpected response code '%s' when expanding server disk '%s' for server '%s'",
354+
response.ResponseCode,
326355
modifyDisk.ID,
327356
serverID,
328357
))
@@ -354,11 +383,11 @@ func processModifyDisks(modifyDisks models.Disks, data *schema.ResourceData, pro
354383

355384
server := resource.(*compute.Server)
356385
propertyHelper.SetDisks(
357-
models.NewDisksFromVirtualMachineDisks(server.Disks),
386+
models.NewDisksFromVirtualMachineSCSIControllers(server.SCSIControllers),
358387
)
359388
propertyHelper.SetPartial(resourceKeyServerDisk)
360389

361-
log.Printf("Server '%s' now has %d disks: %#v.", serverID, len(server.Disks), server.Disks)
390+
log.Printf("Server '%s' now has %d disks: %#v.", serverID, server.SCSIControllers.GetDiskCount(), server.SCSIControllers)
362391

363392
log.Printf(
364393
"Resized disk '%s' for server '%s' (from %d to GB to %d).",
@@ -417,7 +446,7 @@ func processModifyDisks(modifyDisks models.Disks, data *schema.ResourceData, pro
417446

418447
server = resource.(*compute.Server)
419448
propertyHelper.SetDisks(
420-
models.NewDisksFromVirtualMachineDisks(server.Disks),
449+
models.NewDisksFromVirtualMachineSCSIControllers(server.SCSIControllers),
421450
)
422451
propertyHelper.SetPartial(resourceKeyServerDisk)
423452

@@ -488,7 +517,7 @@ func processRemoveDisks(removeDisks models.Disks, data *schema.ResourceData, pro
488517

489518
server := resource.(*compute.Server)
490519
propertyHelper.SetDisks(
491-
models.NewDisksFromVirtualMachineDisks(server.Disks),
520+
models.NewDisksFromVirtualMachineSCSIControllers(server.SCSIControllers),
492521
)
493522
propertyHelper.SetPartial(resourceKeyServerDisk)
494523

@@ -527,7 +556,7 @@ func validateDisks(disks models.Disks) error {
527556
for _, disk := range disks {
528557
_, duplicate := disksByUnitID[disk.SCSIUnitID]
529558
if duplicate {
530-
return fmt.Errorf("Multiple disks with SCSI unit ID '%d'", disk.SCSIUnitID)
559+
return fmt.Errorf("multiple disks with SCSI unit ID %d", disk.SCSIUnitID)
531560
}
532561

533562
disksByUnitID[disk.SCSIUnitID] = disk
@@ -536,6 +565,22 @@ func validateDisks(disks models.Disks) error {
536565
return nil
537566
}
538567

568+
func validateServerDisks(disks models.Disks) error {
569+
err := validateDisks(disks)
570+
if err != nil {
571+
return err
572+
}
573+
574+
for _, disk := range disks {
575+
// Cannot target non-default SCSI controllers if disks are declared directly on the server.
576+
if disk.SCSIBusNumber != 0 {
577+
return fmt.Errorf("unsupported configuration: disk configured to use non-default SCSI bus %d (declare the disk on a ddcloud_storage controller if targeting a SCSI bus other than 0)", disk.SCSIBusNumber)
578+
}
579+
}
580+
581+
return nil
582+
}
583+
539584
func validateDiskSpeed(value interface{}, propertyName string) (messages []string, errors []error) {
540585
if value == nil {
541586
return
@@ -551,7 +596,7 @@ func validateDiskSpeed(value interface{}, propertyName string) (messages []strin
551596
}
552597

553598
switch adapterType {
554-
case compute.ServerDiskSpeedEcomony:
599+
case compute.ServerDiskSpeedEconomy:
555600
case compute.ServerDiskSpeedStandard:
556601
case compute.ServerDiskSpeedHighPerformance:
557602
break

ddcloud/resource_server_network_adapter_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,7 @@ func testCheckDDCloudServerNICDestroy(state *terraform.State) error {
146146
if err != nil {
147147
return nil
148148
}
149-
if server != nil {
149+
if server == nil {
150150
nics := server.Network.AdditionalNetworkAdapters
151151
for _, nic := range nics {
152152
return fmt.Errorf("Nic '%s' still exists", *nic.ID)

0 commit comments

Comments
 (0)