Skip to content

Commit e1b738b

Browse files
committed
api,server,ui: improve listing public ip for associate
Currently, acquire or associate IP action in the UI will load only 500 addresses. ALso, it will load all 500 of them together. This PR uses InfiniteScrollSelect to iteratively load more addresses. To facilitate retrieving both Free and Reserved addresses api and server change has been made for the listPublicIpAddresses API to pass a comma-separated list of states for state parameter. Signed-off-by: Abhishek Kumar <[email protected]>
1 parent 3ddd802 commit e1b738b

File tree

5 files changed

+91
-77
lines changed

5 files changed

+91
-77
lines changed

api/src/main/java/org/apache/cloudstack/api/command/user/address/ListPublicIpAddressesCmd.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ public class ListPublicIpAddressesCmd extends BaseListRetrieveOnlyResourceCountC
5353
@Parameter(name = ApiConstants.ALLOCATED_ONLY, type = CommandType.BOOLEAN, description = "limits search results to allocated public IP addresses")
5454
private Boolean allocatedOnly;
5555

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

5959
@Parameter(name = ApiConstants.FOR_VIRTUAL_NETWORK, type = CommandType.BOOLEAN, description = "the virtual network for the IP address")

server/src/main/java/com/cloud/server/ManagementServerImpl.java

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2451,19 +2451,29 @@ public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListP
24512451
final Long vpcId = cmd.getVpcId();
24522452

24532453
final String state = cmd.getState();
2454+
List<IpAddress.State> states = new ArrayList<>();
2455+
if (StringUtils.isNotBlank(state)) {
2456+
for (String s : StringUtils.split(state, ",")) {
2457+
try {
2458+
states.add(IpAddress.State.valueOf(s));
2459+
} catch (IllegalArgumentException e) {
2460+
throw new InvalidParameterValueException("Invalid state: " + s);
2461+
}
2462+
}
2463+
}
24542464
Boolean isAllocated = cmd.isAllocatedOnly();
24552465
if (isAllocated == null) {
2456-
if (state != null && (state.equalsIgnoreCase(IpAddress.State.Free.name()) || state.equalsIgnoreCase(IpAddress.State.Reserved.name()))) {
2466+
if (states.contains(IpAddress.State.Free) || states.contains(IpAddress.State.Reserved)) {
24572467
isAllocated = Boolean.FALSE;
24582468
} else {
24592469
isAllocated = Boolean.TRUE; // default
24602470
}
24612471
} else {
2462-
if (state != null && (state.equalsIgnoreCase(IpAddress.State.Free.name()) || state.equalsIgnoreCase(IpAddress.State.Reserved.name()))) {
2472+
if (states.contains(IpAddress.State.Free) || states.contains(IpAddress.State.Reserved)) {
24632473
if (isAllocated) {
24642474
throw new InvalidParameterValueException("Conflict: allocatedonly is true but state is Free");
24652475
}
2466-
} else if (state != null && state.equalsIgnoreCase(IpAddress.State.Allocated.name())) {
2476+
} else if (states.contains(IpAddress.State.Allocated)) {
24672477
isAllocated = Boolean.TRUE;
24682478
}
24692479
}
@@ -2543,7 +2553,7 @@ public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListP
25432553
final List<Long> permittedAccounts = new ArrayList<>();
25442554
ListProjectResourcesCriteria listProjectResourcesCriteria = null;
25452555
Boolean isAllocatedOrReserved = false;
2546-
if (isAllocated || IpAddress.State.Reserved.name().equalsIgnoreCase(state)) {
2556+
if (isAllocated || states.contains(IpAddress.State.Allocated)) {
25472557
isAllocatedOrReserved = true;
25482558
}
25492559
if (isAllocatedOrReserved || (vlanType == VlanType.VirtualNetwork && (caller.getType() != Account.Type.ADMIN || cmd.getDomainId() != null))) {
@@ -2559,7 +2569,7 @@ public Pair<List<? extends IpAddress>, Integer> searchForIPAddresses(final ListP
25592569
buildParameters(sb, cmd, vlanType == VlanType.VirtualNetwork ? true : isAllocated);
25602570

25612571
SearchCriteria<IPAddressVO> sc = sb.create();
2562-
setParameters(sc, cmd, vlanType, isAllocated);
2572+
setParameters(sc, cmd, vlanType, isAllocated, states);
25632573

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

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

26432653
SearchCriteria<IPAddressVO> sc2 = sb2.create();
2644-
setParameters(sc2, cmd, vlanType, isAllocated);
2654+
setParameters(sc2, cmd, vlanType, isAllocated, states);
26452655
sc2.setParameters("ids", freeAddrIds.toArray());
26462656
_publicIpAddressDao.buildQuarantineSearchCriteria(sc2);
26472657
addrs.addAll(_publicIpAddressDao.search(sc2, searchFilter)); // Allocated + Free
@@ -2671,7 +2681,7 @@ private void buildParameters(final SearchBuilder<IPAddressVO> sb, final ListPubl
26712681
sb.and("isSourceNat", sb.entity().isSourceNat(), SearchCriteria.Op.EQ);
26722682
sb.and("isStaticNat", sb.entity().isOneToOneNat(), SearchCriteria.Op.EQ);
26732683
sb.and("vpcId", sb.entity().getVpcId(), SearchCriteria.Op.EQ);
2674-
sb.and("state", sb.entity().getState(), SearchCriteria.Op.EQ);
2684+
sb.and("state", sb.entity().getState(), SearchCriteria.Op.IN);
26752685
sb.and("display", sb.entity().isDisplay(), SearchCriteria.Op.EQ);
26762686
sb.and(FOR_SYSTEMVMS, sb.entity().isForSystemVms(), SearchCriteria.Op.EQ);
26772687

@@ -2714,7 +2724,8 @@ private void buildParameters(final SearchBuilder<IPAddressVO> sb, final ListPubl
27142724
}
27152725
}
27162726

2717-
protected void setParameters(SearchCriteria<IPAddressVO> sc, final ListPublicIpAddressesCmd cmd, VlanType vlanType, Boolean isAllocated) {
2727+
protected void setParameters(SearchCriteria<IPAddressVO> sc, final ListPublicIpAddressesCmd cmd, VlanType vlanType,
2728+
Boolean isAllocated, List<IpAddress.State> states) {
27182729
final Object keyword = cmd.getKeyword();
27192730
final Long physicalNetworkId = cmd.getPhysicalNetworkId();
27202731
final Long sourceNetworkId = cmd.getNetworkId();
@@ -2726,7 +2737,6 @@ protected void setParameters(SearchCriteria<IPAddressVO> sc, final ListPublicIpA
27262737
final Boolean sourceNat = cmd.isSourceNat();
27272738
final Boolean staticNat = cmd.isStaticNat();
27282739
final Boolean forDisplay = cmd.getDisplay();
2729-
final String state = cmd.getState();
27302740
final Boolean forSystemVms = cmd.getForSystemVMs();
27312741
final boolean forProvider = cmd.isForProvider();
27322742
final Map<String, String> tags = cmd.getTags();
@@ -2783,13 +2793,14 @@ protected void setParameters(SearchCriteria<IPAddressVO> sc, final ListPublicIpA
27832793
sc.setParameters("display", forDisplay);
27842794
}
27852795

2786-
if (state != null) {
2787-
sc.setParameters("state", state);
2796+
if (CollectionUtils.isNotEmpty(states)) {
2797+
sc.setParameters("state", states.toArray());
27882798
} else if (isAllocated != null && isAllocated) {
27892799
sc.setParameters("state", IpAddress.State.Allocated);
27902800
}
27912801

2792-
if (IpAddressManagerImpl.getSystemvmpublicipreservationmodestrictness().value() && IpAddress.State.Free.name().equalsIgnoreCase(state)) {
2802+
if (IpAddressManagerImpl.getSystemvmpublicipreservationmodestrictness().value() &&
2803+
states.contains(IpAddress.State.Free)) {
27932804
sc.setParameters(FOR_SYSTEMVMS, false);
27942805
} else {
27952806
sc.setParameters(FOR_SYSTEMVMS, forSystemVms);

server/src/test/java/com/cloud/server/ManagementServerImplTest.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.lang.reflect.Field;
2727
import java.util.ArrayList;
2828
import java.util.Arrays;
29+
import java.util.Collections;
2930
import java.util.List;
3031

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

260261
Mockito.verify(sc, Mockito.times(1)).setJoinParameters("vlanSearch", "vlanType", VlanType.VirtualNetwork);
261262
Mockito.verify(sc, Mockito.times(1)).setParameters("display", false);
262263
Mockito.verify(sc, Mockito.times(1)).setParameters("sourceNetworkId", 10L);
263-
Mockito.verify(sc, Mockito.times(1)).setParameters("state", "Free");
264+
Mockito.verify(sc, Mockito.times(1)).setParameters("state", states.toArray());
264265
Mockito.verify(sc, Mockito.times(1)).setParameters("forsystemvms", false);
265266
}
266267

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

283284
Mockito.verify(sc, Mockito.times(1)).setJoinParameters("vlanSearch", "vlanType", VlanType.VirtualNetwork);
284285
Mockito.verify(sc, Mockito.times(1)).setParameters("display", false);
285286
Mockito.verify(sc, Mockito.times(1)).setParameters("sourceNetworkId", 10L);
286-
Mockito.verify(sc, Mockito.times(1)).setParameters("state", "Free");
287+
Mockito.verify(sc, Mockito.times(1)).setParameters("state", states.toArray());
287288
Mockito.verify(sc, Mockito.times(1)).setParameters("forsystemvms", false);
288289
}
289290

@@ -299,13 +300,13 @@ public void setParametersTestWhenStateIsNullAndSystemVmPublicIsFalse() throws No
299300
Mockito.when(cmd.getId()).thenReturn(null);
300301
Mockito.when(cmd.isSourceNat()).thenReturn(null);
301302
Mockito.when(cmd.isStaticNat()).thenReturn(null);
302-
Mockito.when(cmd.getState()).thenReturn(null);
303303
Mockito.when(cmd.getTags()).thenReturn(null);
304-
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.TRUE);
304+
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.TRUE, Collections.emptyList());
305305

306306
Mockito.verify(sc, Mockito.times(1)).setJoinParameters("vlanSearch", "vlanType", VlanType.VirtualNetwork);
307307
Mockito.verify(sc, Mockito.times(1)).setParameters("display", false);
308308
Mockito.verify(sc, Mockito.times(1)).setParameters("sourceNetworkId", 10L);
309+
Mockito.verify(sc, Mockito.times(1)).setParameters("state", IpAddress.State.Allocated);
309310
Mockito.verify(sc, Mockito.times(1)).setParameters("forsystemvms", false);
310311
}
311312

@@ -321,13 +322,13 @@ public void setParametersTestWhenStateIsNullAndSystemVmPublicIsTrue() throws NoS
321322
Mockito.when(cmd.getId()).thenReturn(null);
322323
Mockito.when(cmd.isSourceNat()).thenReturn(null);
323324
Mockito.when(cmd.isStaticNat()).thenReturn(null);
324-
Mockito.when(cmd.getState()).thenReturn(null);
325325
Mockito.when(cmd.getTags()).thenReturn(null);
326-
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.TRUE);
326+
spy.setParameters(sc, cmd, VlanType.VirtualNetwork, Boolean.TRUE, Collections.emptyList());
327327

328328
Mockito.verify(sc, Mockito.times(1)).setJoinParameters("vlanSearch", "vlanType", VlanType.VirtualNetwork);
329329
Mockito.verify(sc, Mockito.times(1)).setParameters("display", false);
330330
Mockito.verify(sc, Mockito.times(1)).setParameters("sourceNetworkId", 10L);
331+
Mockito.verify(sc, Mockito.times(1)).setParameters("state", IpAddress.State.Allocated);
331332
Mockito.verify(sc, Mockito.times(1)).setParameters("forsystemvms", false);
332333
}
333334

ui/src/components/widgets/InfiniteScrollSelect.vue

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
- defaultOption (Object, optional): Preselected object to include initially
4444
- showIcon (Boolean, optional): Whether to show icon for the options. Default is true
4545
- defaultIcon (String, optional): Icon to be shown when there is no resource icon for the option. Default is 'cloud-outlined'
46+
- autoSelectFirstOption (Boolean, optional): Whether to automatically select the first option when options are loaded. Default is false
4647
4748
Events:
4849
- @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
@@ -81,7 +82,7 @@
8182
<resource-icon v-if="option.icon && option.icon.base64image" :image="option.icon.base64image" size="1x" style="margin-right: 5px"/>
8283
<render-icon v-else :icon="defaultIcon" style="margin-right: 5px" />
8384
</span>
84-
<span>{{ option[optionLabelKey] }}</span>
85+
<span>{{ optionLabelFn ? optionLabelFn(option) : option[optionLabelKey] }}</span>
8586
</span>
8687
</a-select-option>
8788
</a-select>
@@ -120,6 +121,10 @@ export default {
120121
type: String,
121122
default: 'name'
122123
},
124+
optionLabelFn: {
125+
type: Function,
126+
default: null
127+
},
123128
defaultOption: {
124129
type: Object,
125130
default: null
@@ -135,6 +140,10 @@ export default {
135140
pageSize: {
136141
type: Number,
137142
default: null
143+
},
144+
autoSelectFirstOption: {
145+
type: Boolean,
146+
default: false
138147
}
139148
},
140149
data () {
@@ -147,11 +156,12 @@ export default {
147156
searchTimer: null,
148157
scrollHandlerAttached: false,
149158
preselectedOptionValue: null,
150-
successiveFetches: 0
159+
successiveFetches: 0,
160+
canSelectFirstOption: false
151161
}
152162
},
153163
created () {
154-
this.addDefaultOptionIfNeeded(true)
164+
this.addDefaultOptionIfNeeded()
155165
},
156166
mounted () {
157167
this.preselectedOptionValue = this.$attrs.value
@@ -208,6 +218,7 @@ export default {
208218
}).catch(error => {
209219
this.$notifyError(error)
210220
}).finally(() => {
221+
this.canSelectFirstOption = true
211222
if (this.successiveFetches === 0) {
212223
this.loading = false
213224
}
@@ -218,6 +229,12 @@ export default {
218229
(Array.isArray(this.preselectedOptionValue) && this.preselectedOptionValue.length === 0) ||
219230
this.successiveFetches >= this.maxSuccessiveFetches) {
220231
this.resetPreselectedOptionValue()
232+
if (!this.canSelectFirstOption && this.autoSelectFirstOption && this.options.length > 0) {
233+
this.$nextTick(() => {
234+
this.preselectedOptionValue = this.options[0][this.optionValueKey]
235+
this.onChange(this.preselectedOptionValue)
236+
})
237+
}
221238
return
222239
}
223240
const matchValue = Array.isArray(this.preselectedOptionValue) ? this.preselectedOptionValue[0] : this.preselectedOptionValue
@@ -239,6 +256,7 @@ export default {
239256
},
240257
addDefaultOptionIfNeeded () {
241258
if (this.defaultOption) {
259+
this.canSelectFirstOption = true
242260
this.options.push(this.defaultOption)
243261
}
244262
},

0 commit comments

Comments
 (0)