Skip to content

Commit c267ad3

Browse files
genegrEugenio Grosso
andauthored
Fix/flasharray delete rename destroy patch conflict (#13049)
Signed-off-by: Eugenio Grosso <eugenio.grosso@gmail.com> Co-authored-by: Eugenio Grosso <egrosso@purestorage.com>
1 parent c165806 commit c267ad3

1 file changed

Lines changed: 53 additions & 14 deletions

File tree

  • plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray

plugins/storage/volume/flasharray/src/main/java/org/apache/cloudstack/storage/datastore/adapter/flasharray/FlashArrayAdapter.java

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
import java.security.KeyManagementException;
2424
import java.security.KeyStoreException;
2525
import java.security.NoSuchAlgorithmException;
26-
import java.text.SimpleDateFormat;
26+
import java.time.ZoneOffset;
27+
import java.time.format.DateTimeFormatter;
2728
import java.util.ArrayList;
2829
import java.util.HashMap;
2930
import java.util.Map;
@@ -88,6 +89,9 @@ public class FlashArrayAdapter implements ProviderAdapter {
8889
static final ObjectMapper mapper = new ObjectMapper();
8990
public String pod = null;
9091
public String hostgroup = null;
92+
private static final DateTimeFormatter DELETION_TIMESTAMP_FORMAT =
93+
DateTimeFormatter.ofPattern("yyyyMMddHHmmss").withZone(ZoneOffset.UTC);
94+
9195
private String username;
9296
private String password;
9397
private String accessToken;
@@ -200,28 +204,63 @@ public void detach(ProviderAdapterContext context, ProviderAdapterDataObject dat
200204

201205
@Override
202206
public void delete(ProviderAdapterContext context, ProviderAdapterDataObject dataObject) {
203-
// first make sure we are disconnected
204-
removeVlunsAll(context, pod, dataObject.getExternalName());
205207
String fullName = normalizeName(pod, dataObject.getExternalName());
206208

207-
FlashArrayVolume volume = new FlashArrayVolume();
209+
// Snapshots live under /volume-snapshots and already use the array's
210+
// reserved form <volume>.<suffix>, which legitimately contains ".".
211+
// The stricter [A-Za-z0-9_-] naming rule applies to regular volume
212+
// names and free-form rename targets, not to these reserved snapshot
213+
// names. Since FlashArray snapshot names are system-defined rather
214+
// than arbitrary rename targets, we skip the usual timestamped rename
215+
// and only mark snapshots destroyed; the array's own ".N" suffix
216+
// already disambiguates them in the recycle bin.
217+
if (dataObject.getType() == ProviderAdapterDataObject.Type.SNAPSHOT) {
218+
try {
219+
FlashArrayVolume destroy = new FlashArrayVolume();
220+
destroy.setDestroyed(true);
221+
PATCH("/volume-snapshots?names=" + fullName, destroy, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
222+
});
223+
} catch (CloudRuntimeException e) {
224+
String msg = e.getMessage();
225+
if (msg != null && (msg.contains("No such volume or snapshot")
226+
|| msg.contains("Volume does not exist"))) {
227+
return;
228+
}
229+
throw e;
230+
}
231+
return;
232+
}
208233

209-
// rename as we delete so it doesn't conflict if the template or volume is ever recreated
210-
// pure keeps the volume(s) around in a Destroyed bucket for a period of time post delete
211-
String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new java.util.Date());
212-
volume.setExternalName(fullName + "-" + timestamp);
234+
// first make sure we are disconnected
235+
removeVlunsAll(context, pod, dataObject.getExternalName());
236+
237+
// Rename then destroy: FlashArray keeps destroyed volumes in a recycle
238+
// bin (default 24h) from which they can be recovered. Renaming with a
239+
// deletion timestamp gives operators a forensic trail when browsing the
240+
// array - they can see when each destroyed copy was deleted on the
241+
// CloudStack side. FlashArray rejects a single PATCH that combines
242+
// {name, destroyed}, so the rename and the destroy must be issued as
243+
// two separate requests each carrying only its own field.
244+
// Use UTC so the rename suffix is stable regardless of the management
245+
// server's local timezone or DST changes - operators correlating the
246+
// CloudStack delete event with the array's audit log get a consistent
247+
// wall-clock value.
248+
String timestamp = DELETION_TIMESTAMP_FORMAT.format(java.time.Instant.now());
249+
String renamedName = fullName + "-" + timestamp;
213250

214251
try {
215-
PATCH("/volumes?names=" + fullName, volume, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
252+
FlashArrayVolume rename = new FlashArrayVolume();
253+
rename.setExternalName(renamedName);
254+
PATCH("/volumes?names=" + fullName, rename, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
216255
});
217256

218-
// now delete it with new name
219-
volume.setDestroyed(true);
220-
221-
PATCH("/volumes?names=" + fullName + "-" + timestamp, volume, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
257+
FlashArrayVolume destroy = new FlashArrayVolume();
258+
destroy.setDestroyed(true);
259+
PATCH("/volumes?names=" + renamedName, destroy, new TypeReference<FlashArrayList<FlashArrayVolume>>() {
222260
});
223261
} catch (CloudRuntimeException e) {
224-
if (e.toString().contains("Volume does not exist")) {
262+
String msg = e.getMessage();
263+
if (msg != null && msg.contains("Volume does not exist")) {
225264
return;
226265
} else {
227266
throw e;

0 commit comments

Comments
 (0)