Skip to content

Commit 031fbf4

Browse files
committed
Merge remote-tracking branch 'apache/4.20' into 4.22
2 parents 2399edd + 8627c60 commit 031fbf4

File tree

19 files changed

+221
-120
lines changed

19 files changed

+221
-120
lines changed

plugins/hypervisors/kvm/pom.xml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,31 @@
6767
<artifactId>java-linstor</artifactId>
6868
<version>${cs.java-linstor.version}</version>
6969
</dependency>
70+
<dependency>
71+
<groupId>com.fasterxml.jackson.core</groupId>
72+
<artifactId>jackson-core</artifactId>
73+
<version>${cs.jackson.version}</version>
74+
</dependency>
75+
<dependency>
76+
<groupId>com.fasterxml.jackson.core</groupId>
77+
<artifactId>jackson-annotations</artifactId>
78+
<version>${cs.jackson.version}</version>
79+
</dependency>
80+
<dependency>
81+
<groupId>com.fasterxml.jackson.core</groupId>
82+
<artifactId>jackson-databind</artifactId>
83+
<version>${cs.jackson.version}</version>
84+
</dependency>
85+
<dependency>
86+
<groupId>com.fasterxml.jackson.datatype</groupId>
87+
<artifactId>jackson-datatype-jsr310</artifactId>
88+
<version>${cs.jackson.version}</version>
89+
</dependency>
90+
<dependency>
91+
<groupId>com.fasterxml.jackson.module</groupId>
92+
<artifactId>jackson-module-jaxb-annotations</artifactId>
93+
<version>${cs.jackson.version}</version>
94+
</dependency>
7095
<dependency>
7196
<groupId>net.java.dev.jna</groupId>
7297
<artifactId>jna</artifactId>

plugins/storage/object/ceph/src/main/java/org/apache/cloudstack/storage/datastore/driver/CephObjectStoreDriverImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ protected AmazonS3 getS3Client(String url, String accessKey, String secretKey) {
350350
new AWSStaticCredentialsProvider(
351351
new BasicAWSCredentials(accessKey, secretKey)))
352352
.withEndpointConfiguration(
353-
new AwsClientBuilder.EndpointConfiguration(url, null))
353+
new AwsClientBuilder.EndpointConfiguration(url, "us-east-1"))
354354
.build();
355355

356356
if (client == null) {

plugins/storage/volume/linstor/src/main/java/org/apache/cloudstack/storage/datastore/driver/LinstorPrimaryDataStoreDriverImpl.java

Lines changed: 55 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,8 @@
6363
import com.cloud.api.storage.LinstorRevertBackupSnapshotCommand;
6464
import com.cloud.configuration.Config;
6565
import com.cloud.host.Host;
66+
import com.cloud.host.HostVO;
67+
import com.cloud.host.Status;
6668
import com.cloud.host.dao.HostDao;
6769
import com.cloud.resource.ResourceState;
6870
import com.cloud.storage.DataStoreRole;
@@ -922,9 +924,10 @@ private String revertSnapshotFromImageStore(
922924
_backupsnapshotwait,
923925
VirtualMachineManager.ExecuteInSequence.value());
924926

925-
Optional<RemoteHostEndPoint> optEP = getDiskfullEP(linstorApi, rscName);
927+
final StoragePool pool = (StoragePool) volumeInfo.getDataStore();
928+
Optional<RemoteHostEndPoint> optEP = getDiskfullEP(linstorApi, pool, rscName);
926929
if (optEP.isEmpty()) {
927-
optEP = getLinstorEP(linstorApi, rscName);
930+
optEP = getLinstorEP(linstorApi, pool, rscName);
928931
}
929932

930933
if (optEP.isPresent()) {
@@ -1064,13 +1067,29 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal
10641067
Answer answer = copyVolume(srcData, dstData);
10651068
res = new CopyCommandResult(null, answer);
10661069
} else {
1067-
Answer answer = new Answer(null, false, "noimpl");
1068-
res = new CopyCommandResult(null, answer);
1069-
res.setResult("Not implemented yet");
1070+
throw new CloudRuntimeException("Not implemented for Linstor primary storage.");
10701071
}
10711072
callback.complete(res);
10721073
}
10731074

1075+
private Host getEnabledClusterHost(StoragePool storagePool, List<String> linstorNodeNames) {
1076+
List<HostVO> csHosts;
1077+
if (storagePool.getClusterId() != null) {
1078+
csHosts = _hostDao.findByClusterId(storagePool.getClusterId());
1079+
} else {
1080+
csHosts = _hostDao.findByDataCenterId(storagePool.getDataCenterId());
1081+
}
1082+
Collections.shuffle(csHosts); // so we do not always pick the same host for operations
1083+
for (HostVO host : csHosts) {
1084+
if (host.getResourceState() == ResourceState.Enabled &&
1085+
host.getStatus() == Status.Up &&
1086+
linstorNodeNames.contains(host.getName())) {
1087+
return host;
1088+
}
1089+
}
1090+
return null;
1091+
}
1092+
10741093
/**
10751094
* Tries to get a Linstor cloudstack end point, that is at least diskless.
10761095
*
@@ -1079,47 +1098,37 @@ public void copyAsync(DataObject srcData, DataObject dstData, AsyncCompletionCal
10791098
* @return Optional RemoteHostEndPoint if one could get found.
10801099
* @throws ApiException
10811100
*/
1082-
private Optional<RemoteHostEndPoint> getLinstorEP(DevelopersApi api, String rscName) throws ApiException {
1101+
private Optional<RemoteHostEndPoint> getLinstorEP(DevelopersApi api, StoragePool storagePool, String rscName)
1102+
throws ApiException {
10831103
List<String> linstorNodeNames = LinstorUtil.getLinstorNodeNames(api);
1084-
Collections.shuffle(linstorNodeNames); // do not always pick the first linstor node
1085-
1086-
Host host = null;
1087-
for (String nodeName : linstorNodeNames) {
1088-
host = _hostDao.findByName(nodeName);
1089-
if (host != null && host.getResourceState() == ResourceState.Enabled) {
1090-
logger.info(String.format("Linstor: Make resource %s available on node %s ...", rscName, nodeName));
1091-
ApiCallRcList answers = api.resourceMakeAvailableOnNode(rscName, nodeName, new ResourceMakeAvailable());
1092-
if (!answers.hasError()) {
1093-
break; // found working host
1094-
} else {
1095-
logger.error(
1096-
String.format("Linstor: Unable to make resource %s on node %s available: %s",
1097-
rscName,
1098-
nodeName,
1099-
LinstorUtil.getBestErrorMessage(answers)));
1100-
}
1104+
Host host = getEnabledClusterHost(storagePool, linstorNodeNames);
1105+
if (host != null) {
1106+
logger.info("Linstor: Make resource {} available on node {} ...", rscName, host.getName());
1107+
ApiCallRcList answers = api.resourceMakeAvailableOnNode(
1108+
rscName, host.getName(), new ResourceMakeAvailable());
1109+
if (answers.hasError()) {
1110+
logger.error("Linstor: Unable to make resource {} on node {} available: {}",
1111+
rscName, host.getName(), LinstorUtil.getBestErrorMessage(answers));
1112+
return Optional.empty();
1113+
} else {
1114+
return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host));
11011115
}
11021116
}
11031117

1104-
if (host == null)
1105-
{
1106-
logger.error("Linstor: Couldn't create a resource on any cloudstack host.");
1107-
return Optional.empty();
1108-
}
1109-
else
1110-
{
1111-
return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host));
1112-
}
1118+
logger.error("Linstor: Couldn't create a resource on any cloudstack host.");
1119+
return Optional.empty();
11131120
}
11141121

1115-
private Optional<RemoteHostEndPoint> getDiskfullEP(DevelopersApi api, String rscName) throws ApiException {
1122+
private Optional<RemoteHostEndPoint> getDiskfullEP(DevelopersApi api, StoragePool storagePool, String rscName)
1123+
throws ApiException {
11161124
List<com.linbit.linstor.api.model.StoragePool> linSPs = LinstorUtil.getDiskfulStoragePools(api, rscName);
11171125
if (linSPs != null) {
1118-
for (com.linbit.linstor.api.model.StoragePool sp : linSPs) {
1119-
Host host = _hostDao.findByName(sp.getNodeName());
1120-
if (host != null && host.getResourceState() == ResourceState.Enabled) {
1121-
return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host));
1122-
}
1126+
List<String> linstorNodeNames = linSPs.stream()
1127+
.map(com.linbit.linstor.api.model.StoragePool::getNodeName)
1128+
.collect(Collectors.toList());
1129+
Host host = getEnabledClusterHost(storagePool, linstorNodeNames);
1130+
if (host != null) {
1131+
return Optional.of(RemoteHostEndPoint.getHypervisorHostEndPoint(host));
11231132
}
11241133
}
11251134
logger.error("Linstor: No diskfull host found.");
@@ -1200,12 +1209,12 @@ private Answer copyTemplate(DataObject srcData, DataObject dstData) {
12001209
VirtualMachineManager.ExecuteInSequence.value());
12011210

12021211
try {
1203-
Optional<RemoteHostEndPoint> optEP = getLinstorEP(api, rscName);
1212+
Optional<RemoteHostEndPoint> optEP = getLinstorEP(api, pool, rscName);
12041213
if (optEP.isPresent()) {
12051214
answer = optEP.get().sendMessage(cmd);
12061215
} else {
1207-
answer = new Answer(cmd, false, "Unable to get matching Linstor endpoint.");
12081216
deleteResourceDefinition(pool, rscName);
1217+
throw new CloudRuntimeException("Unable to get matching Linstor endpoint.");
12091218
}
12101219
} catch (ApiException exc) {
12111220
logger.error("copy template failed: ", exc);
@@ -1242,12 +1251,12 @@ private Answer copyVolume(DataObject srcData, DataObject dstData) {
12421251
Answer answer;
12431252

12441253
try {
1245-
Optional<RemoteHostEndPoint> optEP = getLinstorEP(api, rscName);
1254+
Optional<RemoteHostEndPoint> optEP = getLinstorEP(api, pool, rscName);
12461255
if (optEP.isPresent()) {
12471256
answer = optEP.get().sendMessage(cmd);
12481257
}
12491258
else {
1250-
answer = new Answer(cmd, false, "Unable to get matching Linstor endpoint.");
1259+
throw new CloudRuntimeException("Unable to get matching Linstor endpoint.");
12511260
}
12521261
} catch (ApiException exc) {
12531262
logger.error("copy volume failed: ", exc);
@@ -1280,14 +1289,14 @@ private Answer copyFromTemporaryResource(
12801289
try {
12811290
String devName = restoreResourceFromSnapshot(api, pool, rscName, snapshotName, restoreName);
12821291

1283-
Optional<RemoteHostEndPoint> optEPAny = getLinstorEP(api, restoreName);
1292+
Optional<RemoteHostEndPoint> optEPAny = getLinstorEP(api, pool, restoreName);
12841293
if (optEPAny.isPresent()) {
12851294
// patch the src device path to the temporary linstor resource
12861295
snapshotObject.setPath(devName);
12871296
origCmd.setSrcTO(snapshotObject.getTO());
12881297
answer = optEPAny.get().sendMessage(origCmd);
1289-
} else{
1290-
answer = new Answer(origCmd, false, "Unable to get matching Linstor endpoint.");
1298+
} else {
1299+
throw new CloudRuntimeException("Unable to get matching Linstor endpoint.");
12911300
}
12921301
} finally {
12931302
// delete the temporary resource, noop if already gone
@@ -1349,7 +1358,7 @@ protected Answer copySnapshot(DataObject srcData, DataObject destData) {
13491358
VirtualMachineManager.ExecuteInSequence.value());
13501359
cmd.setOptions(options);
13511360

1352-
Optional<RemoteHostEndPoint> optEP = getDiskfullEP(api, rscName);
1361+
Optional<RemoteHostEndPoint> optEP = getDiskfullEP(api, pool, rscName);
13531362
Answer answer;
13541363
if (optEP.isPresent()) {
13551364
answer = optEP.get().sendMessage(cmd);

pom.xml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@
188188
<org.springframework.version>5.3.26</org.springframework.version>
189189
<cs.ini.version>0.5.4</cs.ini.version>
190190
<cs.caffeine.version>3.1.7</cs.caffeine.version>
191+
<cs.protobuf.version>3.25.5</cs.protobuf.version>
191192
</properties>
192193

193194
<distributionManagement>
@@ -730,6 +731,17 @@
730731
<artifactId>xml-apis</artifactId>
731732
<version>2.0.2</version>
732733
</dependency>
734+
<!-- enforced protobuf version here as mysql-connector-java is pulling older version (3.19.3) -->
735+
<dependency>
736+
<groupId>com.google.protobuf</groupId>
737+
<artifactId>protobuf-java</artifactId>
738+
<version>${cs.protobuf.version}</version>
739+
</dependency>
740+
<dependency>
741+
<groupId>com.google.protobuf</groupId>
742+
<artifactId>protobuf-java-util</artifactId>
743+
<version>${cs.protobuf.version}</version>
744+
</dependency>
733745
<dependency>
734746
<groupId>com.linbit.linstor.api</groupId>
735747
<artifactId>java-linstor</artifactId>

ui/public/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,6 +403,7 @@
403403
"label.app.name": "CloudStack",
404404
"label.application.policy.set": "Application Policy Set",
405405
"label.apply": "Apply",
406+
"label.apply.to.all": "Apply to all",
406407
"label.apply.tungsten.firewall.policy": "Apply Firewall Policy",
407408
"label.apply.tungsten.network.policy": "Apply Network Policy",
408409
"label.apply.tungsten.tag": "Apply tag",
@@ -4034,6 +4035,7 @@
40344035
"message.vnf.no.credentials": "No credentials found for the VNF appliance.",
40354036
"message.vnf.select.networks": "Please select the relevant network for each VNF NIC.",
40364037
"message.volume.desc": "Volume to use as a ROOT disk",
4038+
"message.volume.pool.apply.to.all": "Selected storage pool will be applied to all existing volumes of the instance.",
40374039
"message.volume.state.allocated": "The volume is allocated but has not been created yet.",
40384040
"message.volume.state.attaching": "The volume is attaching to a volume from Ready state.",
40394041
"message.volume.state.copying": "The volume is being copied from the image store to primary storage, in case it's an uploaded volume.",

ui/src/components/view/InfoCard.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@
709709
<div class="resource-detail-item__label">{{ $t('label.storagepool') }}</div>
710710
<div class="resource-detail-item__details">
711711
<database-outlined />
712-
<router-link v-if="!isStatic && $router.resolve('/storagepool/' + resource.storageid).matched[0].redirect !== '/exception/404'" :to="{ path: '/storagepool/' + resource.storageid }">{{ resource.storage || resource.storageid }} </router-link>
712+
<router-link v-if="!isStatic && $router.resolve('/storagepool/' + encodeURIComponent(resource.storageid)).matched[0].redirect !== '/exception/404'" :to="{ path: '/storagepool/' + encodeURIComponent(resource.storageid) }">{{ resource.storage || resource.storageid }} </router-link>
713713
<span v-else>{{ resource.storage || resource.storageid }}</span>
714714
<a-tag style="margin-left: 5px;" v-if="resource.storagetype">
715715
{{ resource.storagetype }}

ui/src/components/view/InstanceVolumesStoragePoolSelectListView.vue

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -206,13 +206,19 @@ export default {
206206
closeVolumeStoragePoolSelector () {
207207
this.selectedVolumeForStoragePoolSelection = {}
208208
},
209-
handleVolumeStoragePoolSelection (volumeId, storagePool) {
209+
handleVolumeStoragePoolSelection (volumeId, storagePool, applyToAll) {
210210
for (const volume of this.volumes) {
211-
if (volume.id === volumeId) {
211+
if (applyToAll) {
212212
volume.selectedstorageid = storagePool.id
213213
volume.selectedstoragename = storagePool.name
214214
volume.selectedstorageclusterid = storagePool.clusterid
215-
break
215+
} else {
216+
if (volume.id === volumeId) {
217+
volume.selectedstorageid = storagePool.id
218+
volume.selectedstoragename = storagePool.name
219+
volume.selectedstorageclusterid = storagePool.clusterid
220+
break
221+
}
216222
}
217223
}
218224
this.updateVolumeToStoragePoolSelection()

ui/src/components/view/ListView.vue

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -161,17 +161,9 @@
161161
>{{ $t(text.toLowerCase()) }}</router-link>
162162
</span>
163163
<span v-else>
164-
<router-link
165-
:to="{ path: $route.path + '/' + record.id }"
166-
v-if="record.id"
167-
>{{ text }}</router-link>
168-
<router-link
169-
:to="{ path: $route.path + '/' + record.name }"
170-
v-else
171-
>{{ text }}</router-link>
172-
<span
173-
v-if="['guestnetwork','vpc'].includes($route.path.split('/')[1]) && record.restartrequired && !record.vpcid"
174-
>
164+
<router-link :to="{ path: $route.path + '/' + encodeURIComponent(record.id) }" v-if="record.id">{{ text }}</router-link>
165+
<router-link :to="{ path: $route.path + '/' + record.name }" v-else>{{ text }}</router-link>
166+
<span v-if="['guestnetwork','vpc'].includes($route.path.split('/')[1]) && record.restartrequired && !record.vpcid">
175167
&nbsp;
176168
<a-tooltip>
177169
<template #title>{{ $t('label.restartrequired') }}</template>
@@ -607,10 +599,7 @@
607599
<span v-else>{{ text }}</span>
608600
</template>
609601
<template v-if="column.key === 'storage'">
610-
<router-link
611-
v-if="record.storageid"
612-
:to="{ path: '/storagepool/' + record.storageid }"
613-
>{{ text }}</router-link>
602+
<router-link v-if="record.storageid" :to="{ path: '/storagepool/' + encodeURIComponent(record.storageid) }">{{ text }}</router-link>
614603
<span v-else>{{ text }}</span>
615604
</template>
616605
<template

ui/src/components/view/VolumeStoragePoolSelectForm.vue

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@
2525
:autoAssignAllowed="autoAssignAllowed"
2626
@select="handleSelect" />
2727

28+
<a-form-item
29+
class="top-spaced">
30+
<template #label>
31+
<tooltip-label :title="$t('label.apply.to.all')" :tooltip="$t('message.volume.pool.apply.to.all')"/>
32+
</template>
33+
<a-switch
34+
v-model:checked="applyToAll" />
35+
</a-form-item>
36+
2837
<a-divider />
2938

3039
<div class="actions">
@@ -36,11 +45,13 @@
3645
</template>
3746

3847
<script>
48+
import TooltipLabel from '@/components/widgets/TooltipLabel'
3949
import StoragePoolSelectView from '@/components/view/StoragePoolSelectView'
4050
4151
export default {
4252
name: 'VolumeStoragePoolSelectionForm',
4353
components: {
54+
TooltipLabel,
4455
StoragePoolSelectView
4556
},
4657
props: {
@@ -70,7 +81,8 @@ export default {
7081
},
7182
data () {
7283
return {
73-
selectedStoragePool: null
84+
selectedStoragePool: null,
85+
applyToAll: false
7486
}
7587
},
7688
watch: {
@@ -95,7 +107,7 @@ export default {
95107
}
96108
},
97109
submitForm () {
98-
this.$emit('select', this.resource.id, this.selectedStoragePool)
110+
this.$emit('select', this.resource.id, this.selectedStoragePool, this.applyToAll)
99111
this.closeModal()
100112
}
101113
}

ui/src/components/view/VolumesTab.vue

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
{{ parseFloat(record.size / (1024.0 * 1024.0 * 1024.0)).toFixed(2) }} GB
4242
</template>
4343
<template v-if="column.key === 'storage'">
44-
<router-link v-if="record.storageid" :to="{ path: '/storagepool/' + record.storageid }">{{ text }}</router-link>
44+
<router-link v-if="record.storageid" :to="{ path: '/storagepool/' + encodeURIComponent(record.storageid) }">{{ text }}</router-link>
4545
<span v-else>{{ text }}</span>
4646
</template>
4747
</template>

0 commit comments

Comments
 (0)