-
Notifications
You must be signed in to change notification settings - Fork 3.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix IndexOutOfBoundsException when storage locations are empty #16439
base: master
Are you sure you want to change the base?
Conversation
storage locations can be empty if not configured. This can cause IndexOutOfBoundsException on nodes where segment assignment can happen.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching this, @abhishekrb19 ! The docs do mention that druid.segmentCache.locations
cannot be null or empty but there doesn't seem to be an existing validation in place.
Just to double check, did you actually run into this issue or did you discover it while reading through the code?
public boolean equals(Object o) | ||
{ | ||
if (this == o) { | ||
return true; | ||
} | ||
if (o == null || getClass() != o.getClass()) { | ||
return false; | ||
} | ||
SegmentChangeStatus that = (SegmentChangeStatus) o; | ||
return state == that.state && Objects.equals(failureCause, that.failureCause); | ||
} | ||
|
||
@Override | ||
public int hashCode() | ||
{ | ||
return Objects.hash(state, failureCause); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would generally prefer to add equals
/hashCode
when it is needed for the actual implementation or if the class has several fields and it makes testing easier.
Since we currently have only 2 fields in this class, we can probably skip it for the time being. Especially since there is only one test which is using this.
if (locations.isEmpty()) { | ||
throw DruidException.forPersona(DruidException.Persona.OPERATOR) | ||
.ofCategory(DruidException.Category.NOT_FOUND) | ||
.build("storage locations are empty. At least one storage path must be specified " |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.build("storage locations are empty. At least one storage path must be specified " | |
.build("Storage locations are empty. At least one storage path must be specified " |
@@ -54,7 +55,10 @@ private SegmentChangeStatus( | |||
@JsonProperty("failureCause") @Nullable String failureCause | |||
) | |||
{ | |||
this.state = Preconditions.checkNotNull(state, "state must be non-null"); | |||
if (state == null) { | |||
throw DruidException.defensive("state must be non-null"); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
throw DruidException.defensive("state must be non-null"); | |
throw DruidException.defensive("SegmentChangeStatus must have a non-null state"); |
@@ -567,6 +568,48 @@ public void testProcessBatchDuplicateLoadRequestsWhenFirstRequestFailsSecondRequ | |||
segmentLoadDropHandler.stop(); | |||
} | |||
|
|||
@Test(timeout = 60_000L) | |||
public void testProcessBatchWithDefaultNoStorageLocations() throws Exception |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
public void testProcessBatchWithDefaultNoStorageLocations() throws Exception | |
public void testProcessBatchWithEmptyStorageLocations() throws Exception |
List<DataSegmentChangeResponse> result = future.get(); | ||
Assert.assertEquals(1, result.size()); | ||
|
||
SegmentChangeStatus expectedFailedStatus = SegmentChangeStatus.failed( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It doesn't make sense to fail every segment load request. The historical should just fail to start up in the first place.
One way to do that would be to add a constructor to SegmentLoaderConfig
and do the validation there. Another method could be to add a validateLocations()
method to SegmentLoaderConfig
and invoke it from a (probably guice-injected) class which is initialized on startup.
Thanks for the review, @kfaraz. After digging up a bit more, I see there are some more changes needed to make it work for tasks, where the task locations aren't used/injected correctly in
I ran into Marking this as draft until the other things are fleshed out and then I will rethink this approach to enforce the storage locations validation for historicals. |
Storage locations can be empty if not configured on a server. If left unconfigured, this can cause an
IndexOutOfBoundsException
when segments are assigned to a server by the coordinator.Changes:
locations.isEmpty()
ingetInfoDir()
. I'm not sure there exists a meaningful default we can assume when locations is not configured.equals()
andhashCode()
implementations inSegmentChangeStatus
to validate equality.Preconditions.checkNotNull()
to aDruidException.defensive()
.This PR has: