Skip to content

Commit 7e2e778

Browse files
Add & Remove PowerFlex/ScaleIO MDMs while preparing & unpreparing the storage SDC connections (instead of start & stop scini)
1 parent 8af08dd commit 7e2e778

File tree

18 files changed

+279
-67
lines changed

18 files changed

+279
-67
lines changed

core/src/main/java/com/cloud/agent/api/UnprepareStorageClientCommand.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,22 @@
1919

2020
package com.cloud.agent.api;
2121

22+
import java.util.Map;
23+
2224
import com.cloud.storage.Storage.StoragePoolType;
2325

2426
public class UnprepareStorageClientCommand extends Command {
2527
private StoragePoolType poolType;
2628
private String poolUuid;
29+
private Map<String, String> details;
2730

2831
public UnprepareStorageClientCommand() {
2932
}
3033

31-
public UnprepareStorageClientCommand(StoragePoolType poolType, String poolUuid) {
34+
public UnprepareStorageClientCommand(StoragePoolType poolType, String poolUuid, Map<String, String> details) {
3235
this.poolType = poolType;
3336
this.poolUuid = poolUuid;
37+
this.details = details;
3438
}
3539

3640
@Override
@@ -45,4 +49,8 @@ public StoragePoolType getPoolType() {
4549
public String getPoolUuid() {
4650
return poolUuid;
4751
}
52+
53+
public Map<String, String> getDetails() {
54+
return details;
55+
}
4856
}

engine/storage/snapshot/src/main/java/org/apache/cloudstack/storage/vmsnapshot/ScaleIOVMSnapshotStrategy.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
3434
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClientConnectionPool;
3535
import org.apache.cloudstack.storage.datastore.db.PrimaryDataStoreDao;
36+
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailVO;
3637
import org.apache.cloudstack.storage.datastore.db.StoragePoolDetailsDao;
3738
import org.apache.cloudstack.storage.datastore.db.StoragePoolVO;
3839
import org.apache.cloudstack.storage.datastore.util.ScaleIOUtil;
@@ -300,7 +301,11 @@ public boolean revertVMSnapshot(VMSnapshot vmSnapshot) {
300301
srcSnapshotDestVolumeMap.put(srcSnapshotVolumeId, destVolumeId);
301302
}
302303

303-
String systemId = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
304+
String systemId = null;
305+
StoragePoolDetailVO systemIdDetail = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
306+
if (systemIdDetail != null) {
307+
systemId = systemIdDetail.getValue();
308+
}
304309
if (systemId == null) {
305310
throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool for reverting VM snapshot: " + vmSnapshot.getName());
306311
}
@@ -379,7 +384,11 @@ public boolean deleteVMSnapshot(VMSnapshot vmSnapshot) {
379384
try {
380385
List<VolumeObjectTO> volumeTOs = vmSnapshotHelper.getVolumeTOList(vmSnapshot.getVmId());
381386
Long storagePoolId = vmSnapshotHelper.getStoragePoolForVM(userVm.getId());
382-
String systemId = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID).getValue();
387+
String systemId = null;
388+
StoragePoolDetailVO systemIdDetail = storagePoolDetailsDao.findDetail(storagePoolId, ScaleIOGatewayClient.STORAGE_POOL_SYSTEM_ID);
389+
if (systemIdDetail != null) {
390+
systemId = systemIdDetail.getValue();
391+
}
383392
if (systemId == null) {
384393
throw new CloudRuntimeException("Failed to get the system id for PowerFlex storage pool for deleting VM snapshot: " + vmSnapshot.getName());
385394
}

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnprepareStorageClientCommandWrapper.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public class LibvirtUnprepareStorageClientCommandWrapper extends CommandWrapper<
3434
@Override
3535
public Answer execute(UnprepareStorageClientCommand cmd, LibvirtComputingResource libvirtComputingResource) {
3636
final KVMStoragePoolManager storagePoolMgr = libvirtComputingResource.getStoragePoolMgr();
37-
Pair<Boolean, String> unprepareStorageClientResult = storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid());
37+
Pair<Boolean, String> unprepareStorageClientResult = storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid(), cmd.getDetails());
3838
if (!unprepareStorageClientResult.first()) {
3939
String msg = unprepareStorageClientResult.second();
4040
logger.debug("Couldn't unprepare storage client, due to: " + msg);

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/KVMStoragePoolManager.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,8 @@ public Ternary<Boolean, Map<String, String>, String> prepareStorageClient(Storag
482482
return adaptor.prepareStorageClient(type, uuid, details);
483483
}
484484

485-
public Pair<Boolean, String> unprepareStorageClient(StoragePoolType type, String uuid) {
485+
public Pair<Boolean, String> unprepareStorageClient(StoragePoolType type, String uuid, Map<String, String> details) {
486486
StorageAdaptor adaptor = getStorageAdaptor(type);
487-
return adaptor.unprepareStorageClient(type, uuid);
487+
return adaptor.unprepareStorageClient(type, uuid, details);
488488
}
489489
}

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptor.java

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -584,14 +584,24 @@ public Ternary<Boolean, Map<String, String>, String> prepareStorageClient(Storag
584584
if (!ScaleIOUtil.startSDCService()) {
585585
return new Ternary<>(false, null, "Couldn't start SDC service on host");
586586
}
587-
} else if (!ScaleIOUtil.restartSDCService()) {
588-
return new Ternary<>(false, null, "Couldn't restart SDC service on host");
587+
}
588+
589+
if (details != null && details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_MDMS)) {
590+
// Assuming SDC service is started, add mdms
591+
String mdms = details.get(ScaleIOGatewayClient.STORAGE_POOL_MDMS);
592+
String[] mdmAddresses = mdms.split(",");
593+
if (mdmAddresses.length > 0) {
594+
ScaleIOUtil.addMdms(Arrays.asList(mdmAddresses));
595+
if (!ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
596+
return new Ternary<>(false, null, "Failed to add MDMs");
597+
}
598+
}
589599
}
590600

591601
return new Ternary<>( true, getSDCDetails(details), "Prepared client successfully");
592602
}
593603

594-
public Pair<Boolean, String> unprepareStorageClient(Storage.StoragePoolType type, String uuid) {
604+
public Pair<Boolean, String> unprepareStorageClient(Storage.StoragePoolType type, String uuid, Map<String, String> details) {
595605
if (!ScaleIOUtil.isSDCServiceInstalled()) {
596606
logger.debug("SDC service not installed on host, no need to unprepare the SDC client");
597607
return new Pair<>(true, "SDC service not installed on host, no need to unprepare the SDC client");
@@ -602,8 +612,19 @@ public Pair<Boolean, String> unprepareStorageClient(Storage.StoragePoolType type
602612
return new Pair<>(true, "SDC service not enabled on host, no need to unprepare the SDC client");
603613
}
604614

605-
if (!ScaleIOUtil.stopSDCService()) {
606-
return new Pair<>(false, "Couldn't stop SDC service on host");
615+
if (details != null && details.containsKey(ScaleIOGatewayClient.STORAGE_POOL_MDMS)) {
616+
String mdms = details.get(ScaleIOGatewayClient.STORAGE_POOL_MDMS);
617+
String[] mdmAddresses = mdms.split(",");
618+
if (mdmAddresses.length > 0) {
619+
if (!ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
620+
return new Pair<>(true, "MDM not added, no need to unprepare the SDC client");
621+
}
622+
623+
ScaleIOUtil.removeMdms(Arrays.asList(mdmAddresses));
624+
if (ScaleIOUtil.mdmAdded(mdmAddresses[0])) {
625+
return new Pair<>(false, "Failed to remove MDMs, unable to unprepare the SDC client");
626+
}
627+
}
607628
}
608629

609630
return new Pair<>(true, "Unprepared SDC client successfully");

plugins/hypervisors/kvm/src/main/java/com/cloud/hypervisor/kvm/storage/StorageAdaptor.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,9 +133,10 @@ default Ternary<Boolean, Map<String, String>, String> prepareStorageClient(Stora
133133
* Unprepares the storage client.
134134
* @param type type of the storage pool
135135
* @param uuid uuid of the storage pool
136+
* @param details any details of the storage pool that are required for client unpreparation
136137
* @return status, & message in case failed
137138
*/
138-
default Pair<Boolean, String> unprepareStorageClient(StoragePoolType type, String uuid) {
139+
default Pair<Boolean, String> unprepareStorageClient(StoragePoolType type, String uuid, Map<String, String> details) {
139140
return new Pair<>(true, "");
140141
}
141142
}

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/resource/wrapper/LibvirtUnprepareStorageClientCommandWrapperTest.java

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
package com.cloud.hypervisor.kvm.resource.wrapper;
1616

17+
import java.util.HashMap;
18+
import java.util.Map;
19+
20+
import org.apache.cloudstack.storage.datastore.client.ScaleIOGatewayClient;
1721
import org.junit.Assert;
1822
import org.junit.Test;
1923
import org.junit.runner.RunWith;
@@ -45,10 +49,13 @@ public void testUnprepareStorageClientSuccess() {
4549
UnprepareStorageClientCommand cmd = Mockito.mock(UnprepareStorageClientCommand.class);
4650
Mockito.when(cmd.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex);
4751
Mockito.when(cmd.getPoolUuid()).thenReturn(poolUuid);
52+
Map<String, String> details = new HashMap<>();
53+
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, "1.1.1.1,2.2.2.2");
54+
Mockito.when(cmd.getDetails()).thenReturn(details);
4855

4956
KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class);
5057
Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr);
51-
Mockito.when(storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid())).thenReturn(new Pair<>(true, ""));
58+
Mockito.when(storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid(), cmd.getDetails())).thenReturn(new Pair<>(true, ""));
5259

5360
UnprepareStorageClientAnswer result = (UnprepareStorageClientAnswer) libvirtUnprepareStorageClientCommandWrapperSpy.execute(cmd, libvirtComputingResourceMock);
5461

@@ -60,10 +67,13 @@ public void testUnprepareStorageClientFailure() {
6067
UnprepareStorageClientCommand cmd = Mockito.mock(UnprepareStorageClientCommand.class);
6168
Mockito.when(cmd.getPoolType()).thenReturn(Storage.StoragePoolType.PowerFlex);
6269
Mockito.when(cmd.getPoolUuid()).thenReturn(poolUuid);
70+
Map<String, String> details = new HashMap<>();
71+
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, "1.1.1.1,2.2.2.2");
72+
Mockito.when(cmd.getDetails()).thenReturn(details);
6373

6474
KVMStoragePoolManager storagePoolMgr = Mockito.mock(KVMStoragePoolManager.class);
6575
Mockito.when(libvirtComputingResourceMock.getStoragePoolMgr()).thenReturn(storagePoolMgr);
66-
Mockito.when(storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid())).thenReturn(new Pair<>(false, "Unprepare storage client failed"));
76+
Mockito.when(storagePoolMgr.unprepareStorageClient(cmd.getPoolType(), cmd.getPoolUuid(), cmd.getDetails())).thenReturn(new Pair<>(false, "Unprepare storage client failed"));
6777

6878
UnprepareStorageClientAnswer result = (UnprepareStorageClientAnswer) libvirtUnprepareStorageClientCommandWrapperSpy.execute(cmd, libvirtComputingResourceMock);
6979

plugins/hypervisors/kvm/src/test/java/com/cloud/hypervisor/kvm/storage/ScaleIOStorageAdaptorTest.java

Lines changed: 19 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -93,34 +93,6 @@ public void testPrepareStorageClient_SDCServiceNotEnabled() {
9393
Assert.assertEquals("SDC service not enabled on host", result.third());
9494
}
9595

96-
@Test
97-
public void testPrepareStorageClient_SDCServiceNotRestarted() {
98-
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
99-
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
100-
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-active scini"))).thenReturn(0);
101-
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl restart scini"))).thenReturn(1);
102-
103-
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>());
104-
105-
Assert.assertFalse(result.first());
106-
Assert.assertNull(result.second());
107-
Assert.assertEquals("Couldn't restart SDC service on host", result.third());
108-
}
109-
110-
@Test
111-
public void testPrepareStorageClient_SDCServiceRestarted() {
112-
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
113-
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
114-
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-active scini"))).thenReturn(0);
115-
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl restart scini"))).thenReturn(0);
116-
117-
Ternary<Boolean, Map<String, String>, String> result = scaleIOStorageAdaptor.prepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, new HashMap<>());
118-
119-
Assert.assertTrue(result.first());
120-
Assert.assertNotNull(result.second());
121-
Assert.assertTrue(result.second().isEmpty());
122-
}
123-
12496
@Test
12597
public void testPrepareStorageClient_SDCServiceNotStarted() {
12698
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
@@ -181,45 +153,53 @@ public void testPrepareStorageClient_SDCServiceStartedReturnSDCGuid() {
181153

182154
@Test
183155
public void testUnprepareStorageClient_SDCServiceNotInstalled() {
156+
Map<String, String> details = new HashMap<>();
184157
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(4);
185158

186-
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid);
159+
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, details);
187160

188161
Assert.assertTrue(result.first());
189162
Assert.assertEquals("SDC service not installed on host, no need to unprepare the SDC client", result.second());
190163
}
191164

192165
@Test
193166
public void testUnprepareStorageClient_SDCServiceNotEnabled() {
167+
Map<String, String> details = new HashMap<>();
194168
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
195169
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(1);
196170

197-
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid);
171+
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, details);
198172

199173
Assert.assertTrue(result.first());
200174
Assert.assertEquals("SDC service not enabled on host, no need to unprepare the SDC client", result.second());
201175
}
202176

203177
@Test
204-
public void testUnprepareStorageClient_SDCServiceNotStopped() {
178+
public void testUnprepareStorageClient_MDMNotAdded() {
179+
Map<String, String> details = new HashMap<>();
180+
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, "1.1.1.1,2.2.2.2");
205181
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
206182
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
207-
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl stop scini"))).thenReturn(1);
183+
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 1.1.1.1"))).thenReturn("MDM-ID 71fd458f0775010f SDC ID 4421a91a00000000 INSTALLATION ID 204930df2cbcaf8e IPs [0]-3.3.3.3 [1]-4.4.4.4");
208184

209-
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid);
185+
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, details);
210186

211-
Assert.assertFalse(result.first());
212-
Assert.assertEquals("Couldn't stop SDC service on host", result.second());
187+
Assert.assertTrue(result.first());
188+
Assert.assertEquals("MDM not added, no need to unprepare the SDC client", result.second());
213189
}
214190

215191
@Test
216-
public void testUnprepareStorageClient_SDCServiceStopped() {
192+
public void testUnprepareStorageClient_RemoveMDMFailed() {
193+
Map<String, String> details = new HashMap<>();
194+
details.put(ScaleIOGatewayClient.STORAGE_POOL_MDMS, "1.1.1.1,2.2.2.2");
217195
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl status scini"))).thenReturn(3);
218196
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl is-enabled scini"))).thenReturn(0);
219-
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl stop scini"))).thenReturn(0);
197+
when(Script.runSimpleBashScriptForExitValue(Mockito.eq("systemctl restart scini"))).thenReturn(0);
198+
when(Script.runSimpleBashScript(Mockito.eq("/opt/emc/scaleio/sdc/bin/drv_cfg --query_mdms|grep 1.1.1.1"))).thenReturn("MDM-ID 71fd458f0775010f SDC ID 4421a91a00000000 INSTALLATION ID 204930df2cbcaf8e IPs [0]-1.1.1.1 [1]-2.2.2.2");
220199

221-
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid);
200+
Pair<Boolean, String> result = scaleIOStorageAdaptor.unprepareStorageClient(Storage.StoragePoolType.PowerFlex, poolUuid, details);
222201

223-
Assert.assertTrue(result.first());
202+
Assert.assertFalse(result.first());
203+
Assert.assertEquals("Failed to remove MDMs, unable to unprepare the SDC client", result.second());
224204
}
225205
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Licensed to the Apache Software Foundation (ASF) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The ASF licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package org.apache.cloudstack.storage.datastore.api;
19+
20+
public class StorageConfiguration {
21+
String systemId;
22+
Long mdmPort;
23+
String[] mdmAddresses;
24+
25+
public String getSystemId() {
26+
return systemId;
27+
}
28+
29+
public void setSystemId(String systemId) {
30+
this.systemId = systemId;
31+
}
32+
33+
public Long getMdmPort() {
34+
return mdmPort;
35+
}
36+
37+
public void setMdmPort(Long mdmPort) {
38+
this.mdmPort = mdmPort;
39+
}
40+
41+
public String[] getMdmAddresses() {
42+
return mdmAddresses;
43+
}
44+
45+
public void setMdmAddresses(String[] mdmAddresses) {
46+
this.mdmAddresses = mdmAddresses;
47+
}
48+
}

plugins/storage/volume/scaleio/src/main/java/org/apache/cloudstack/storage/datastore/api/StoragePool.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,15 @@
1717

1818
package org.apache.cloudstack.storage.datastore.api;
1919

20+
import java.util.List;
21+
2022
public class StoragePool {
2123
String id;
2224
String name;
2325
String mediaType;
2426
String protectionDomainId;
2527
String systemId;
28+
List<String> mdmAddresses;
2629
StoragePoolStatistics statistics;
2730

2831
public String getId() {
@@ -65,6 +68,14 @@ public void setSystemId(String systemId) {
6568
this.systemId = systemId;
6669
}
6770

71+
public List<String> getMdmAddresses() {
72+
return mdmAddresses;
73+
}
74+
75+
public void setMdmAddresses(List<String> mdmAddresses) {
76+
this.mdmAddresses = mdmAddresses;
77+
}
78+
6879
public StoragePoolStatistics getStatistics() {
6980
return statistics;
7081
}

0 commit comments

Comments
 (0)