Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ public class ListPublicIpAddressesCmd extends BaseListRetrieveOnlyResourceCountC
@Parameter(name = ApiConstants.ALLOCATED_ONLY, type = CommandType.BOOLEAN, description = "limits search results to allocated public IP addresses")
private Boolean allocatedOnly;

@Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "lists all public IP addresses by state")
@Parameter(name = ApiConstants.STATE, type = CommandType.STRING, description = "lists all public IP addresses by state. A comma-separated list of states can be passed")
private String state;

@Parameter(name = ApiConstants.FOR_VIRTUAL_NETWORK, type = CommandType.BOOLEAN, description = "the virtual network for the IP address")
Expand Down
37 changes: 24 additions & 13 deletions server/src/main/java/com/cloud/server/ManagementServerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -2451,19 +2451,29 @@ public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListP
final Long vpcId = cmd.getVpcId();

final String state = cmd.getState();
List<IpAddress.State> states = new ArrayList<>();
if (StringUtils.isNotBlank(state)) {
for (String s : StringUtils.split(state, ",")) {
try {
states.add(IpAddress.State.valueOf(s));
} catch (IllegalArgumentException e) {
throw new InvalidParameterValueException("Invalid state: " + s);
}
}
}
Boolean isAllocated = cmd.isAllocatedOnly();
if (isAllocated == null) {
if (state != null && (state.equalsIgnoreCase(IpAddress.State.Free.name()) || state.equalsIgnoreCase(IpAddress.State.Reserved.name()))) {
if (states.contains(IpAddress.State.Free) || states.contains(IpAddress.State.Reserved)) {
isAllocated = Boolean.FALSE;
} else {
isAllocated = Boolean.TRUE; // default
}
} else {
if (state != null && (state.equalsIgnoreCase(IpAddress.State.Free.name()) || state.equalsIgnoreCase(IpAddress.State.Reserved.name()))) {
if (states.contains(IpAddress.State.Free) || states.contains(IpAddress.State.Reserved)) {
if (isAllocated) {
throw new InvalidParameterValueException("Conflict: allocatedonly is true but state is Free");
}
} else if (state != null && state.equalsIgnoreCase(IpAddress.State.Allocated.name())) {
} else if (states.contains(IpAddress.State.Allocated)) {
isAllocated = Boolean.TRUE;
}
}
Comment on lines +2454 to 2479
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this logic to its own method?

Expand Down Expand Up @@ -2543,7 +2553,7 @@ public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListP
final List<Long> permittedAccounts = new ArrayList<>();
ListProjectResourcesCriteria listProjectResourcesCriteria = null;
Boolean isAllocatedOrReserved = false;
if (isAllocated || IpAddress.State.Reserved.name().equalsIgnoreCase(state)) {
if (isAllocated || states.contains(IpAddress.State.Reserved)) {
isAllocatedOrReserved = true;
}
if (isAllocatedOrReserved || (vlanType == VlanType.VirtualNetwork && (caller.getType() != Account.Type.ADMIN || cmd.getDomainId() != null))) {
Expand All @@ -2559,7 +2569,7 @@ public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListP
buildParameters(sb, cmd, vlanType == VlanType.VirtualNetwork ? true : isAllocated);

SearchCriteria<IPAddressVO> sc = sb.create();
setParameters(sc, cmd, vlanType, isAllocated);
setParameters(sc, cmd, vlanType, isAllocated, states);

if (isAllocatedOrReserved || (vlanType == VlanType.VirtualNetwork && (caller.getType() != Account.Type.ADMIN || cmd.getDomainId() != null))) {
_accountMgr.buildACLSearchCriteria(sc, domainId, isRecursive, permittedAccounts, listProjectResourcesCriteria);
Expand Down Expand Up @@ -2628,7 +2638,7 @@ public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListP
buildParameters(searchBuilder, cmd, false);

SearchCriteria<IPAddressVO> searchCriteria = searchBuilder.create();
setParameters(searchCriteria, cmd, vlanType, false);
setParameters(searchCriteria, cmd, vlanType, false, states);
searchCriteria.setParameters("state", IpAddress.State.Free.name());
addrs.addAll(_publicIpAddressDao.search(searchCriteria, searchFilter)); // Free IPs on shared network
}
Expand All @@ -2641,7 +2651,7 @@ public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListP
sb2.and("quarantinedPublicIpsIdsNIN", sb2.entity().getId(), SearchCriteria.Op.NIN);

SearchCriteria<IPAddressVO> sc2 = sb2.create();
setParameters(sc2, cmd, vlanType, isAllocated);
setParameters(sc2, cmd, vlanType, isAllocated, states);
sc2.setParameters("ids", freeAddrIds.toArray());
_publicIpAddressDao.buildQuarantineSearchCriteria(sc2);
addrs.addAll(_publicIpAddressDao.search(sc2, searchFilter)); // Allocated + Free
Expand Down Expand Up @@ -2671,7 +2681,7 @@ private void buildParameters(final SearchBuilder<IPAddressVO> sb, final ListPubl
sb.and("isSourceNat", sb.entity().isSourceNat(), SearchCriteria.Op.EQ);
sb.and("isStaticNat", sb.entity().isOneToOneNat(), SearchCriteria.Op.EQ);
sb.and("vpcId", sb.entity().getVpcId(), SearchCriteria.Op.EQ);
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
sb.and("state", sb.entity().getState(), SearchCriteria.Op.IN);
sb.and("display", sb.entity().isDisplay(), SearchCriteria.Op.EQ);
sb.and(FOR_SYSTEMVMS, sb.entity().isForSystemVms(), SearchCriteria.Op.EQ);

Expand Down Expand Up @@ -2714,7 +2724,8 @@ private void buildParameters(final SearchBuilder<IPAddressVO> sb, final ListPubl
}
}

protected void setParameters(SearchCriteria<IPAddressVO> sc, final ListPublicIpAddressesCmd cmd, VlanType vlanType, Boolean isAllocated) {
protected void setParameters(SearchCriteria<IPAddressVO> sc, final ListPublicIpAddressesCmd cmd, VlanType vlanType,
Boolean isAllocated, List<IpAddress.State> states) {
final Object keyword = cmd.getKeyword();
final Long physicalNetworkId = cmd.getPhysicalNetworkId();
final Long sourceNetworkId = cmd.getNetworkId();
Expand All @@ -2726,7 +2737,6 @@ protected void setParameters(SearchCriteria<IPAddressVO> sc, final ListPublicIpA
final Boolean sourceNat = cmd.isSourceNat();
final Boolean staticNat = cmd.isStaticNat();
final Boolean forDisplay = cmd.getDisplay();
final String state = cmd.getState();
final Boolean forSystemVms = cmd.getForSystemVMs();
final boolean forProvider = cmd.isForProvider();
final Map<String, String> tags = cmd.getTags();
Expand Down Expand Up @@ -2783,13 +2793,14 @@ protected void setParameters(SearchCriteria<IPAddressVO> sc, final ListPublicIpA
sc.setParameters("display", forDisplay);
}

if (state != null) {
sc.setParameters("state", state);
if (CollectionUtils.isNotEmpty(states)) {
sc.setParameters("state", states.toArray());
} else if (isAllocated != null && isAllocated) {
sc.setParameters("state", IpAddress.State.Allocated);
}

if (IpAddressManagerImpl.getSystemvmpublicipreservationmodestrictness().value() && IpAddress.State.Free.name().equalsIgnoreCase(state)) {
if (IpAddressManagerImpl.getSystemvmpublicipreservationmodestrictness().value() &&
states.contains(IpAddress.State.Free)) {
sc.setParameters(FOR_SYSTEMVMS, false);
} else {
sc.setParameters(FOR_SYSTEMVMS, forSystemVms);
Expand Down
21 changes: 11 additions & 10 deletions server/src/test/java/com/cloud/server/ManagementServerImplTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import org.apache.cloudstack.annotation.dao.AnnotationDao;
Expand Down Expand Up @@ -253,14 +254,14 @@ public void setParametersTestWhenStateIsFreeAndSystemVmPublicIsTrue() throws Ill
Mockito.when(cmd.getId()).thenReturn(null);
Mockito.when(cmd.isSourceNat()).thenReturn(null);
Mockito.when(cmd.isStaticNat()).thenReturn(null);
Mockito.when(cmd.getState()).thenReturn(IpAddress.State.Free.name());
Mockito.when(cmd.getTags()).thenReturn(null);
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.FALSE);
List<IpAddress.State> states = Collections.singletonList(IpAddress.State.Free);
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.FALSE, states);

Mockito.verify(sc, Mockito.times(1)).setJoinParameters("vlanSearch", "vlanType", VlanType.VirtualNetwork);
Mockito.verify(sc, Mockito.times(1)).setParameters("display", false);
Mockito.verify(sc, Mockito.times(1)).setParameters("sourceNetworkId", 10L);
Mockito.verify(sc, Mockito.times(1)).setParameters("state", "Free");
Mockito.verify(sc, Mockito.times(1)).setParameters("state", states.toArray());
Mockito.verify(sc, Mockito.times(1)).setParameters("forsystemvms", false);
}

Expand All @@ -276,14 +277,14 @@ public void setParametersTestWhenStateIsFreeAndSystemVmPublicIsFalse() throws No
Mockito.when(cmd.getId()).thenReturn(null);
Mockito.when(cmd.isSourceNat()).thenReturn(null);
Mockito.when(cmd.isStaticNat()).thenReturn(null);
Mockito.when(cmd.getState()).thenReturn(IpAddress.State.Free.name());
Mockito.when(cmd.getTags()).thenReturn(null);
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.FALSE);
List<IpAddress.State> states = Collections.singletonList(IpAddress.State.Free);
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.FALSE, states);

Mockito.verify(sc, Mockito.times(1)).setJoinParameters("vlanSearch", "vlanType", VlanType.VirtualNetwork);
Mockito.verify(sc, Mockito.times(1)).setParameters("display", false);
Mockito.verify(sc, Mockito.times(1)).setParameters("sourceNetworkId", 10L);
Mockito.verify(sc, Mockito.times(1)).setParameters("state", "Free");
Mockito.verify(sc, Mockito.times(1)).setParameters("state", states.toArray());
Mockito.verify(sc, Mockito.times(1)).setParameters("forsystemvms", false);
}

Expand All @@ -299,13 +300,13 @@ public void setParametersTestWhenStateIsNullAndSystemVmPublicIsFalse() throws No
Mockito.when(cmd.getId()).thenReturn(null);
Mockito.when(cmd.isSourceNat()).thenReturn(null);
Mockito.when(cmd.isStaticNat()).thenReturn(null);
Mockito.when(cmd.getState()).thenReturn(null);
Mockito.when(cmd.getTags()).thenReturn(null);
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.TRUE);
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.TRUE, Collections.emptyList());

Mockito.verify(sc, Mockito.times(1)).setJoinParameters("vlanSearch", "vlanType", VlanType.VirtualNetwork);
Mockito.verify(sc, Mockito.times(1)).setParameters("display", false);
Mockito.verify(sc, Mockito.times(1)).setParameters("sourceNetworkId", 10L);
Mockito.verify(sc, Mockito.times(1)).setParameters("state", IpAddress.State.Allocated);
Mockito.verify(sc, Mockito.times(1)).setParameters("forsystemvms", false);
}

Expand All @@ -321,13 +322,13 @@ public void setParametersTestWhenStateIsNullAndSystemVmPublicIsTrue() throws NoS
Mockito.when(cmd.getId()).thenReturn(null);
Mockito.when(cmd.isSourceNat()).thenReturn(null);
Mockito.when(cmd.isStaticNat()).thenReturn(null);
Mockito.when(cmd.getState()).thenReturn(null);
Mockito.when(cmd.getTags()).thenReturn(null);
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.TRUE);
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.TRUE, Collections.emptyList());

Mockito.verify(sc, Mockito.times(1)).setJoinParameters("vlanSearch", "vlanType", VlanType.VirtualNetwork);
Mockito.verify(sc, Mockito.times(1)).setParameters("display", false);
Mockito.verify(sc, Mockito.times(1)).setParameters("sourceNetworkId", 10L);
Mockito.verify(sc, Mockito.times(1)).setParameters("state", IpAddress.State.Allocated);
Mockito.verify(sc, Mockito.times(1)).setParameters("forsystemvms", false);
}

Expand Down
25 changes: 22 additions & 3 deletions ui/src/components/widgets/InfiniteScrollSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,12 @@
- apiParams (Object, optional): Additional parameters passed to the API
- resourceType (String, required): The key in the API response containing the resource array (e.g., 'account')
- optionValueKey (String, optional): Property to use as the value for options (e.g., 'name'). Default is 'id'
- optionLabelFn (Function, optional): Function to generate the label for options. Receives the option object as argument. If provided, takes precedence over optionLabelKey. Preferred over optionLabelKey.
- optionLabelKey (String, optional): Property to use as the label for options (e.g., 'name'). Default is 'name'
- defaultOption (Object, optional): Preselected object to include initially
- showIcon (Boolean, optional): Whether to show icon for the options. Default is true
- defaultIcon (String, optional): Icon to be shown when there is no resource icon for the option. Default is 'cloud-outlined'
- autoSelectFirstOption (Boolean, optional): Whether to automatically select the first option when options are loaded. Default is false. Works only when there is no preselected value or defaultOption

Events:
- @change-option-value (Function): Emits the selected option value(s) when value(s) changes. Do not use @change as it will give warnings and may not work
Expand Down Expand Up @@ -81,7 +83,7 @@
<resource-icon v-if="option.icon && option.icon.base64image" :image="option.icon.base64image" size="1x" style="margin-right: 5px"/>
<render-icon v-else :icon="defaultIcon" style="margin-right: 5px" />
</span>
<span>{{ option[optionLabelKey] }}</span>
<span>{{ optionLabelFn ? optionLabelFn(option) : option[optionLabelKey] }}</span>
</span>
</a-select-option>
</a-select>
Expand Down Expand Up @@ -120,6 +122,10 @@ export default {
type: String,
default: 'name'
},
optionLabelFn: {
type: Function,
default: null
},
defaultOption: {
type: Object,
default: null
Expand All @@ -135,6 +141,10 @@ export default {
pageSize: {
type: Number,
default: null
},
autoSelectFirstOption: {
type: Boolean,
default: false
}
},
data () {
Expand All @@ -147,11 +157,12 @@ export default {
searchTimer: null,
scrollHandlerAttached: false,
preselectedOptionValue: null,
successiveFetches: 0
successiveFetches: 0,
canSelectFirstOption: true
}
},
created () {
this.addDefaultOptionIfNeeded(true)
this.addDefaultOptionIfNeeded()
},
mounted () {
this.preselectedOptionValue = this.$attrs.value
Expand Down Expand Up @@ -208,6 +219,7 @@ export default {
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.canSelectFirstOption = false
if (this.successiveFetches === 0) {
this.loading = false
}
Expand All @@ -218,6 +230,12 @@ export default {
(Array.isArray(this.preselectedOptionValue) && this.preselectedOptionValue.length === 0) ||
this.successiveFetches >= this.maxSuccessiveFetches) {
this.resetPreselectedOptionValue()
if (this.canSelectFirstOption && this.autoSelectFirstOption && this.options.length > 0) {
this.$nextTick(() => {
this.preselectedOptionValue = this.options[0][this.optionValueKey]
this.onChange(this.preselectedOptionValue)
})
}
return
}
const matchValue = Array.isArray(this.preselectedOptionValue) ? this.preselectedOptionValue[0] : this.preselectedOptionValue
Expand All @@ -239,6 +257,7 @@ export default {
},
addDefaultOptionIfNeeded () {
if (this.defaultOption) {
this.canSelectFirstOption = false
this.options.push(this.defaultOption)
}
},
Expand Down
Loading
Loading