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
50 changes: 33 additions & 17 deletions server/src/main/java/com/cloud/server/ManagementServerImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -659,6 +659,7 @@
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.EnumUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;

Expand Down Expand Up @@ -2411,6 +2412,22 @@ public Pair<List<? extends ConfigurationGroup>, Integer> listConfigurationGroups
return new Pair<>(result.first(), result.second());
}

protected List<IpAddress.State> getStatesForIpAddressSearch(final ListPublicIpAddressesCmd cmd) {
final String statesStr = cmd.getState();
final List<IpAddress.State> states = new ArrayList<>();
if (StringUtils.isBlank(statesStr)) {
return states;
}
for (String s : StringUtils.split(statesStr, ",")) {
IpAddress.State state = EnumUtils.getEnumIgnoreCase(IpAddress.State.class, s.trim());
if (state == null) {
throw new InvalidParameterValueException("Invalid state: " + s);
}
states.add(state);
}
return states;
}

@Override
public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListPublicIpAddressesCmd cmd) {
final Long associatedNetworkId = cmd.getAssociatedNetworkId();
Expand All @@ -2421,20 +2438,20 @@ public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListP
final Long networkId = cmd.getNetworkId();
final Long vpcId = cmd.getVpcId();

final String state = cmd.getState();
final List<IpAddress.State> states = getStatesForIpAddressSearch(cmd);
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;
}
}
Expand Down Expand Up @@ -2513,10 +2530,8 @@ public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListP
Boolean isRecursive = cmd.isRecursive();
final List<Long> permittedAccounts = new ArrayList<>();
ListProjectResourcesCriteria listProjectResourcesCriteria = null;
boolean isAllocatedOrReserved = false;
if (isAllocated || IpAddress.State.Reserved.name().equalsIgnoreCase(state)) {
isAllocatedOrReserved = true;
}
boolean isAllocatedOrReserved = isAllocated ||
(states.size() == 1 && IpAddress.State.Reserved.equals(states.get(0)));
if (isAllocatedOrReserved || (vlanType == VlanType.VirtualNetwork && (caller.getType() != Account.Type.ADMIN || cmd.getDomainId() != null))) {
final Ternary<Long, Boolean, ListProjectResourcesCriteria> domainIdRecursiveListProject = new Ternary<>(cmd.getDomainId(), cmd.isRecursive(),
null);
Expand All @@ -2530,7 +2545,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 @@ -2598,7 +2613,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 @@ -2611,7 +2626,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 @@ -2641,7 +2656,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 @@ -2684,7 +2699,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 @@ -2695,7 +2711,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 @@ -2752,13 +2767,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
66 changes: 56 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 @@ -258,14 +259,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 @@ -281,14 +282,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 @@ -304,13 +305,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 @@ -326,13 +327,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 Expand Up @@ -1033,4 +1034,49 @@ public void testGetExternalVmConsole() {
Assert.assertNotNull(spy.getExternalVmConsole(virtualMachine, host));
Mockito.verify(extensionManager).getInstanceConsole(virtualMachine, host);
}

@Test
public void getStatesForIpAddressSearchReturnsValidStates() {
ListPublicIpAddressesCmd cmd = Mockito.mock(ListPublicIpAddressesCmd.class);
Mockito.when(cmd.getState()).thenReturn("Allocated ,free");
List<IpAddress.State> result = spy.getStatesForIpAddressSearch(cmd);
Assert.assertEquals(2, result.size());
Assert.assertTrue(result.contains(IpAddress.State.Allocated));
Assert.assertTrue(result.contains(IpAddress.State.Free));
}

@Test
public void getStatesForIpAddressSearchReturnsEmptyListForNullState() {
ListPublicIpAddressesCmd cmd = Mockito.mock(ListPublicIpAddressesCmd.class);
Mockito.when(cmd.getState()).thenReturn(null);
List<IpAddress.State> result = spy.getStatesForIpAddressSearch(cmd);
Assert.assertTrue(result.isEmpty());
}

@Test
public void getStatesForIpAddressSearchReturnsEmptyListForBlankState() {
ListPublicIpAddressesCmd cmd = Mockito.mock(ListPublicIpAddressesCmd.class);
Mockito.when(cmd.getState()).thenReturn(" ");
List<IpAddress.State> result = spy.getStatesForIpAddressSearch(cmd);
Assert.assertTrue(result.isEmpty());
}

@Test(expected = InvalidParameterValueException.class)
public void getStatesForIpAddressSearchThrowsExceptionForInvalidState() {
ListPublicIpAddressesCmd cmd = Mockito.mock(ListPublicIpAddressesCmd.class);
Mockito.when(cmd.getState()).thenReturn("InvalidState");
spy.getStatesForIpAddressSearch(cmd);
}

@Test
public void getStatesForIpAddressSearchHandlesMixedValidAndInvalidStates() {
ListPublicIpAddressesCmd cmd = Mockito.mock(ListPublicIpAddressesCmd.class);
Mockito.when(cmd.getState()).thenReturn("Allocated,InvalidState");
try {
spy.getStatesForIpAddressSearch(cmd);
Assert.fail("Expected InvalidParameterValueException to be thrown");
} catch (InvalidParameterValueException e) {
Assert.assertEquals("Invalid state: InvalidState", e.getMessage());
}
}
}
24 changes: 21 additions & 3 deletions ui/src/components/widgets/InfiniteScrollSelect.vue
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
- 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

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 +82,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 +121,10 @@ export default {
type: String,
default: 'name'
},
optionLabelFn: {
type: Function,
default: null
},
defaultOption: {
type: Object,
default: null
Expand All @@ -135,6 +140,10 @@ export default {
pageSize: {
type: Number,
default: null
},
autoSelectFirstOption: {
type: Boolean,
default: false
}
},
data () {
Expand All @@ -147,11 +156,12 @@ export default {
searchTimer: null,
scrollHandlerAttached: false,
preselectedOptionValue: null,
successiveFetches: 0
successiveFetches: 0,
canSelectFirstOption: false
}
},
created () {
this.addDefaultOptionIfNeeded(true)
this.addDefaultOptionIfNeeded()
},
mounted () {
this.preselectedOptionValue = this.$attrs.value
Expand Down Expand Up @@ -208,6 +218,7 @@ export default {
}).catch(error => {
this.$notifyError(error)
}).finally(() => {
this.canSelectFirstOption = true
if (this.successiveFetches === 0) {
this.loading = false
}
Expand All @@ -218,6 +229,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 +256,7 @@ export default {
},
addDefaultOptionIfNeeded () {
if (this.defaultOption) {
this.canSelectFirstOption = true
this.options.push(this.defaultOption)
}
},
Expand Down
Loading
Loading