Commit 22d320fd authored by Spiros Koulouzis's avatar Spiros Koulouzis

added exception if no valid credentials are found. Tested with exogeni provider

parent e2ec6617
...@@ -46,4 +46,5 @@ ...@@ -46,4 +46,5 @@
/manager-services/target/ /manager-services/target/
/drip-manager-services/nbproject/ /drip-manager-services/nbproject/
/drip-manager-services/target/ /drip-manager-services/target/
/drip-provisioner/nbproject/ /drip-provisioner/nbproject/
\ No newline at end of file /fake_credentials/copy_of_test-geni.jks
\ No newline at end of file
...@@ -159,6 +159,7 @@ public class ToscaHelperTest { ...@@ -159,6 +159,7 @@ public class ToscaHelperTest {
/** /**
* Test of getTemplateVMsForVMTopology method, of class ToscaHelper. * Test of getTemplateVMsForVMTopology method, of class ToscaHelper.
* @throws java.lang.Exception
*/ */
@Test @Test
public void testGetTemplateVMsForVMTopology() throws Exception { public void testGetTemplateVMsForVMTopology() throws Exception {
......
...@@ -91,7 +91,7 @@ public class DRIPService { ...@@ -91,7 +91,7 @@ public class DRIPService {
this.requestQeueName = requestQeueName; this.requestQeueName = requestQeueName;
} }
private Credential getBestCredential(NodeTemplate vmTopology, List<Credential> credentials) { private Credential getBestCredential(List<Credential> credentials) {
return credentials.get(0); return credentials.get(0);
} }
...@@ -100,12 +100,17 @@ public class DRIPService { ...@@ -100,12 +100,17 @@ public class DRIPService {
List<Credential> credentials = null; List<Credential> credentials = null;
for (NodeTemplateMap vmTopologyMap : vmTopologies) { for (NodeTemplateMap vmTopologyMap : vmTopologies) {
String provider = helper.getTopologyProvider(vmTopologyMap); String provider = helper.getTopologyProvider(vmTopologyMap);
credentials = credentialService.findByProvider(provider.toLowerCase()); if (needsCredentials(provider)) {
if (credentials != null && credentials.size() > 0) { credentials = credentialService.findByProvider(provider);
Credential credential = getBestCredential(vmTopologyMap.getNodeTemplate(), credentials); if (credentials == null || credentials.size() <= 0) {
vmTopologyMap = helper.setCredentialsInVMTopology(vmTopologyMap, credential); throw new Exception("Provider: " + provider + " needs credentials but non clould be found");
toscaTemplate = helper.setVMTopologyInToscaTemplate(toscaTemplate, vmTopologyMap); } else {
Credential credential = getBestCredential(credentials);
vmTopologyMap = helper.setCredentialsInVMTopology(vmTopologyMap, credential);
toscaTemplate = helper.setVMTopologyInToscaTemplate(toscaTemplate, vmTopologyMap);
}
} }
} }
return toscaTemplate; return toscaTemplate;
} }
...@@ -144,4 +149,13 @@ public class DRIPService { ...@@ -144,4 +149,13 @@ public class DRIPService {
return toscaTemplate; return toscaTemplate;
} }
private boolean needsCredentials(String provider) {
switch (provider) {
case "local":
return false;
default:
return true;
}
}
} }
...@@ -38,7 +38,6 @@ import java.util.logging.Logger; ...@@ -38,7 +38,6 @@ import java.util.logging.Logger;
import nl.uva.sne.drip.Swagger2SpringBoot; import nl.uva.sne.drip.Swagger2SpringBoot;
import nl.uva.sne.drip.commons.utils.Converter; import nl.uva.sne.drip.commons.utils.Converter;
import nl.uva.sne.drip.configuration.MongoConfig; import nl.uva.sne.drip.configuration.MongoConfig;
import nl.uva.sne.drip.model.cloud.storm.CloudDB;
import nl.uva.sne.drip.model.tosca.Credential; import nl.uva.sne.drip.model.tosca.Credential;
import org.junit.Assert; import org.junit.Assert;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
...@@ -239,6 +238,7 @@ public class ServiceTests { ...@@ -239,6 +238,7 @@ public class ServiceTests {
/** /**
* Test of getAllIds method, of class ToscaTemplateService. * Test of getAllIds method, of class ToscaTemplateService.
* @throws java.lang.Exception
*/ */
@Test @Test
public void testToscaTemplateServiceGetAllIds() throws Exception { public void testToscaTemplateServiceGetAllIds() throws Exception {
...@@ -322,6 +322,7 @@ public class ServiceTests { ...@@ -322,6 +322,7 @@ public class ServiceTests {
/** /**
* Test of deleteByID method, of class CredentialService. * Test of deleteByID method, of class CredentialService.
* @throws com.fasterxml.jackson.core.JsonProcessingException
*/ */
@Test @Test
public void testCredentialServiceDeleteByID() throws JsonProcessingException { public void testCredentialServiceDeleteByID() throws JsonProcessingException {
......
...@@ -2,14 +2,8 @@ ...@@ -2,14 +2,8 @@
<project version="4"> <project version="4">
<component name="ChangeListManager"> <component name="ChangeListManager">
<list default="true" id="e478ccae-5352-4e8e-9efb-3f5cda44e877" name="Default Changelist" comment=""> <list default="true" id="e478ccae-5352-4e8e-9efb-3f5cda44e877" name="Default Changelist" comment="">
<change beforePath="$PROJECT_DIR$/../drip-commons/src/main/java/nl/uva/sne/drip/commons/utils/Converter.java" beforeDir="false" afterPath="$PROJECT_DIR$/../drip-commons/src/main/java/nl/uva/sne/drip/commons/utils/Converter.java" afterDir="false" /> <change beforePath="$PROJECT_DIR$/../.gitignore" beforeDir="false" afterPath="$PROJECT_DIR$/../.gitignore" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../drip-commons/src/main/java/nl/uva/sne/drip/commons/utils/ToscaHelper.java" beforeDir="false" afterPath="$PROJECT_DIR$/../drip-commons/src/main/java/nl/uva/sne/drip/commons/utils/ToscaHelper.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../drip-manager/src/main/java/nl/uva/sne/drip/api/CredentialApi.java" beforeDir="false" afterPath="$PROJECT_DIR$/../drip-manager/src/main/java/nl/uva/sne/drip/api/CredentialApi.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../drip-manager/src/main/java/nl/uva/sne/drip/api/CredentialApiController.java" beforeDir="false" afterPath="$PROJECT_DIR$/../drip-manager/src/main/java/nl/uva/sne/drip/api/CredentialApiController.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../drip-manager/src/main/java/nl/uva/sne/drip/api/DeployerApi.java" beforeDir="false" afterPath="$PROJECT_DIR$/../drip-manager/src/main/java/nl/uva/sne/drip/api/DeployerApi.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../drip-manager/src/test/java/nl/uva/sne/drip/service/ServiceTests.java" beforeDir="false" afterPath="$PROJECT_DIR$/../drip-manager/src/test/java/nl/uva/sne/drip/service/ServiceTests.java" afterDir="false" />
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" /> <change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../openAPI/API/CONF-3.0.0-swagger.yaml" beforeDir="false" afterPath="$PROJECT_DIR$/../openAPI/API/CONF-3.0.0-swagger.yaml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/../sure_tosca-flask-server/sure_tosca/service/tosca_template_service.py" beforeDir="false" afterPath="$PROJECT_DIR$/../sure_tosca-flask-server/sure_tosca/service/tosca_template_service.py" afterDir="false" /> <change beforePath="$PROJECT_DIR$/../sure_tosca-flask-server/sure_tosca/service/tosca_template_service.py" beforeDir="false" afterPath="$PROJECT_DIR$/../sure_tosca-flask-server/sure_tosca/service/tosca_template_service.py" afterDir="false" />
</list> </list>
<option name="SHOW_DIALOG" value="false" /> <option name="SHOW_DIALOG" value="false" />
...@@ -39,7 +33,7 @@ ...@@ -39,7 +33,7 @@
</component> </component>
<component name="PropertiesComponent"> <component name="PropertiesComponent">
<property name="RunOnceActivity.ShowReadmeOnStart" value="true" /> <property name="RunOnceActivity.ShowReadmeOnStart" value="true" />
<property name="last_opened_file_path" value="$PROJECT_DIR$" /> <property name="last_opened_file_path" value="$PROJECT_DIR$/../../devops-prep/week2Lab1/python-flask-server-generated" />
<property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" /> <property name="settings.editor.selected.configurable" value="com.jetbrains.python.configuration.PyActiveSdkModuleConfigurable" />
</component> </component>
<component name="RecentsManager"> <component name="RecentsManager">
...@@ -117,9 +111,9 @@ ...@@ -117,9 +111,9 @@
</configuration> </configuration>
<list> <list>
<item itemvalue="Python.__main__" /> <item itemvalue="Python.__main__" />
<item itemvalue="Python tests.Unittests for test_planner.MyTestCase.test_planner" />
<item itemvalue="Python tests.Unittests for test_planner.MyTestCase.test_something" /> <item itemvalue="Python tests.Unittests for test_planner.MyTestCase.test_something" />
<item itemvalue="Python tests.Unittests in test_planner.py" /> <item itemvalue="Python tests.Unittests in test_planner.py" />
<item itemvalue="Python tests.Unittests for test_planner.MyTestCase.test_planner" />
</list> </list>
<recent_temporary> <recent_temporary>
<list> <list>
...@@ -272,30 +266,31 @@ ...@@ -272,30 +266,31 @@
<screen x="67" y="34" width="2493" height="1406" /> <screen x="67" y="34" width="2493" height="1406" />
</state> </state>
<state width="2155" height="582" key="DebuggerActiveHint/67.34.2493.1406@67.34.2493.1406" timestamp="1577715024259" /> <state width="2155" height="582" key="DebuggerActiveHint/67.34.2493.1406@67.34.2493.1406" timestamp="1577715024259" />
<state x="1043" y="437" width="530" height="598" key="FileChooserDialogImpl" timestamp="1575907769017"> <state x="792" y="334" width="827" height="663" key="FileChooserDialogImpl" timestamp="1578326180157">
<screen x="67" y="34" width="2493" height="1406" /> <screen x="67" y="34" width="1853" height="1046" />
</state> </state>
<state x="792" y="334" width="827" height="663" key="FileChooserDialogImpl/67.34.1853.1046@67.34.1853.1046" timestamp="1578326180157" />
<state x="1043" y="437" width="530" height="598" key="FileChooserDialogImpl/67.34.2493.1406@67.34.2493.1406" timestamp="1575907769017" /> <state x="1043" y="437" width="530" height="598" key="FileChooserDialogImpl/67.34.2493.1406@67.34.2493.1406" timestamp="1575907769017" />
<state width="2465" height="413" key="GridCell.Tab.0.bottom" timestamp="1577720249205"> <state width="2465" height="382" key="GridCell.Tab.0.bottom" timestamp="1578250751968">
<screen x="67" y="34" width="2493" height="1406" /> <screen x="67" y="34" width="2493" height="1406" />
</state> </state>
<state width="1825" height="283" key="GridCell.Tab.0.bottom/67.34.1853.1046@67.34.1853.1046" timestamp="1576165782177" /> <state width="1825" height="283" key="GridCell.Tab.0.bottom/67.34.1853.1046@67.34.1853.1046" timestamp="1576165782177" />
<state width="2465" height="413" key="GridCell.Tab.0.bottom/67.34.2493.1406@67.34.2493.1406" timestamp="1577720249205" /> <state width="2465" height="382" key="GridCell.Tab.0.bottom/67.34.2493.1406@67.34.2493.1406" timestamp="1578250751968" />
<state width="2465" height="413" key="GridCell.Tab.0.center" timestamp="1577720249202"> <state width="2465" height="382" key="GridCell.Tab.0.center" timestamp="1578250751968">
<screen x="67" y="34" width="2493" height="1406" /> <screen x="67" y="34" width="2493" height="1406" />
</state> </state>
<state width="1825" height="283" key="GridCell.Tab.0.center/67.34.1853.1046@67.34.1853.1046" timestamp="1576165782175" /> <state width="1825" height="283" key="GridCell.Tab.0.center/67.34.1853.1046@67.34.1853.1046" timestamp="1576165782175" />
<state width="2465" height="413" key="GridCell.Tab.0.center/67.34.2493.1406@67.34.2493.1406" timestamp="1577720249202" /> <state width="2465" height="382" key="GridCell.Tab.0.center/67.34.2493.1406@67.34.2493.1406" timestamp="1578250751968" />
<state width="2465" height="413" key="GridCell.Tab.0.left" timestamp="1577720249200"> <state width="2465" height="382" key="GridCell.Tab.0.left" timestamp="1578250751968">
<screen x="67" y="34" width="2493" height="1406" /> <screen x="67" y="34" width="2493" height="1406" />
</state> </state>
<state width="1825" height="283" key="GridCell.Tab.0.left/67.34.1853.1046@67.34.1853.1046" timestamp="1576165782174" /> <state width="1825" height="283" key="GridCell.Tab.0.left/67.34.1853.1046@67.34.1853.1046" timestamp="1576165782174" />
<state width="2465" height="413" key="GridCell.Tab.0.left/67.34.2493.1406@67.34.2493.1406" timestamp="1577720249200" /> <state width="2465" height="382" key="GridCell.Tab.0.left/67.34.2493.1406@67.34.2493.1406" timestamp="1578250751968" />
<state width="2465" height="413" key="GridCell.Tab.0.right" timestamp="1577720249203"> <state width="2465" height="382" key="GridCell.Tab.0.right" timestamp="1578250751968">
<screen x="67" y="34" width="2493" height="1406" /> <screen x="67" y="34" width="2493" height="1406" />
</state> </state>
<state width="1825" height="283" key="GridCell.Tab.0.right/67.34.1853.1046@67.34.1853.1046" timestamp="1576165782176" /> <state width="1825" height="283" key="GridCell.Tab.0.right/67.34.1853.1046@67.34.1853.1046" timestamp="1576165782176" />
<state width="2465" height="413" key="GridCell.Tab.0.right/67.34.2493.1406@67.34.2493.1406" timestamp="1577720249203" /> <state width="2465" height="382" key="GridCell.Tab.0.right/67.34.2493.1406@67.34.2493.1406" timestamp="1578250751968" />
<state width="2465" height="413" key="GridCell.Tab.1.bottom" timestamp="1577720249209"> <state width="2465" height="413" key="GridCell.Tab.1.bottom" timestamp="1577720249209">
<screen x="67" y="34" width="2493" height="1406" /> <screen x="67" y="34" width="2493" height="1406" />
</state> </state>
...@@ -316,10 +311,10 @@ ...@@ -316,10 +311,10 @@
<screen x="67" y="34" width="2493" height="1406" /> <screen x="67" y="34" width="2493" height="1406" />
</state> </state>
<state x="679" y="283" key="SettingsEditor/67.34.2493.1406@67.34.2493.1406" timestamp="1575885393075" /> <state x="679" y="283" key="SettingsEditor/67.34.2493.1406@67.34.2493.1406" timestamp="1575885393075" />
<state x="893" y="526" key="com.intellij.ide.util.TipDialog" timestamp="1577206106759"> <state x="681" y="400" key="com.intellij.ide.util.TipDialog" timestamp="1578326162404">
<screen x="67" y="34" width="2493" height="1406" /> <screen x="67" y="34" width="1853" height="1046" />
</state> </state>
<state x="681" y="400" key="com.intellij.ide.util.TipDialog/67.34.1853.1046@67.34.1853.1046" timestamp="1576760567200" /> <state x="681" y="400" key="com.intellij.ide.util.TipDialog/67.34.1853.1046@67.34.1853.1046" timestamp="1578326162404" />
<state x="893" y="526" key="com.intellij.ide.util.TipDialog/67.34.2493.1406@67.34.2493.1406" timestamp="1577206106759" /> <state x="893" y="526" key="com.intellij.ide.util.TipDialog/67.34.2493.1406@67.34.2493.1406" timestamp="1577206106759" />
<state x="882" y="239" width="862" height="993" key="find.popup" timestamp="1576090708880"> <state x="882" y="239" width="862" height="993" key="find.popup" timestamp="1576090708880">
<screen x="67" y="34" width="2493" height="1406" /> <screen x="67" y="34" width="2493" height="1406" />
......
...@@ -63,7 +63,7 @@ ...@@ -63,7 +63,7 @@
<dependency> <dependency>
<groupId>nl.uva.sne.zh</groupId> <groupId>nl.uva.sne.zh</groupId>
<artifactId>CloudsStorm</artifactId> <artifactId>CloudsStorm</artifactId>
<version>b.1.0</version> <version>b.1.1</version>
</dependency> </dependency>
</dependencies> </dependencies>
......
...@@ -17,9 +17,7 @@ import com.jcraft.jsch.KeyPair; ...@@ -17,9 +17,7 @@ import com.jcraft.jsch.KeyPair;
import java.io.File; import java.io.File;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
...@@ -27,6 +25,7 @@ import java.util.Objects; ...@@ -27,6 +25,7 @@ import java.util.Objects;
import java.util.Properties; import java.util.Properties;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;
import nl.uva.sne.drip.commons.utils.Converter;
import nl.uva.sne.drip.commons.utils.ToscaHelper; import nl.uva.sne.drip.commons.utils.ToscaHelper;
import nl.uva.sne.drip.model.cloud.storm.CloudsStormVM; import nl.uva.sne.drip.model.cloud.storm.CloudsStormVM;
import nl.uva.sne.drip.model.NodeTemplateMap; import nl.uva.sne.drip.model.NodeTemplateMap;
...@@ -60,6 +59,7 @@ class CloudStormService { ...@@ -60,6 +59,7 @@ class CloudStormService {
private final CloudStormDAO cloudStormDAO; private final CloudStormDAO cloudStormDAO;
private final ObjectMapper objectMapper; private final ObjectMapper objectMapper;
private final String cloudStormDBPath; private final String cloudStormDBPath;
private final String SUB_TOPOLOGY_NAME = "subTopology";
CloudStormService(Properties properties, ToscaTemplate toscaTemplate) throws IOException, JsonProcessingException, ApiException { CloudStormService(Properties properties, ToscaTemplate toscaTemplate) throws IOException, JsonProcessingException, ApiException {
// this.toscaTemplate = toscaTemplate; // this.toscaTemplate = toscaTemplate;
...@@ -157,7 +157,7 @@ class CloudStormService { ...@@ -157,7 +157,7 @@ class CloudStormService {
String provider = helper.getTopologyProvider(nodeTemplateMap); String provider = helper.getTopologyProvider(nodeTemplateMap);
cloudsStormSubTopology.setDomain(domain); cloudsStormSubTopology.setDomain(domain);
cloudsStormSubTopology.setCloudProvider(provider); cloudsStormSubTopology.setCloudProvider(provider);
cloudsStormSubTopology.setTopology("sub-topology" + i); cloudsStormSubTopology.setTopology(SUB_TOPOLOGY_NAME + i);
cloudsStormSubTopology.setStatus(CloudsStormSubTopology.StatusEnum.FRESH); cloudsStormSubTopology.setStatus(CloudsStormSubTopology.StatusEnum.FRESH);
CloudsStormVMs cloudsStormVMs = new CloudsStormVMs(); CloudsStormVMs cloudsStormVMs = new CloudsStormVMs();
...@@ -175,7 +175,7 @@ class CloudStormService { ...@@ -175,7 +175,7 @@ class CloudStormService {
j++; j++;
} }
cloudsStormVMs.setVms(vms); cloudsStormVMs.setVms(vms);
objectMapper.writeValue(new File(tempInputDirPath + File.separator + "sub-topology" + i + ".yml"), cloudsStormVMs); objectMapper.writeValue(new File(tempInputDirPath + File.separator + SUB_TOPOLOGY_NAME + i + ".yml"), cloudsStormVMs);
cloudsStormVMsList.add(cloudsStormVMs); cloudsStormVMsList.add(cloudsStormVMs);
cloudsStormSubTopologies.add(cloudsStormSubTopology); cloudsStormSubTopologies.add(cloudsStormSubTopology);
i++; i++;
...@@ -207,7 +207,6 @@ class CloudStormService { ...@@ -207,7 +207,6 @@ class CloudStormService {
int i = 0; int i = 0;
for (NodeTemplateMap vmTopologyMap : vmTopologiesMaps) { for (NodeTemplateMap vmTopologyMap : vmTopologiesMaps) {
Credential toscaCredentials = helper.getCredentialsFromVMTopology(vmTopologyMap); Credential toscaCredentials = helper.getCredentialsFromVMTopology(vmTopologyMap);
CloudCred cloudStormCredential = new CloudCred(); CloudCred cloudStormCredential = new CloudCred();
cloudStormCredential.setCloudProvider(toscaCredentials.getCloudProviderName()); cloudStormCredential.setCloudProvider(toscaCredentials.getCloudProviderName());
String credInfoFile = toscaCredentials.getCloudProviderName() + i + ".yml"; String credInfoFile = toscaCredentials.getCloudProviderName() + i + ".yml";
...@@ -223,16 +222,15 @@ class CloudStormService { ...@@ -223,16 +222,15 @@ class CloudStormService {
objectMapper.writeValue(new File(credentialsTempInputDirPath + File.separator + "cred.yml"), cloudStormCredentials); objectMapper.writeValue(new File(credentialsTempInputDirPath + File.separator + "cred.yml"), cloudStormCredentials);
} }
private CredentialInfo getCloudStormCredentialInfo(Credential toscaCredentials, String tmpPath) throws FileNotFoundException { private CredentialInfo getCloudStormCredentialInfo(Credential toscaCredentials, String tmpPath) throws FileNotFoundException, IOException {
CredentialInfo cloudStormCredentialInfo = new CredentialInfo(); CredentialInfo cloudStormCredentialInfo = new CredentialInfo();
switch (toscaCredentials.getCloudProviderName().toLowerCase()) { switch (toscaCredentials.getCloudProviderName().toLowerCase()) {
case "exogeni": case "exogeni":
String base64Keystore = toscaCredentials.getKeys().get("keystore"); String base64Keystore = toscaCredentials.getKeys().get("keystore");
byte[] decoded = Base64.getDecoder().decode(base64Keystore);
try (PrintWriter out = new PrintWriter(tmpPath + File.separator + "user.jks")) { Converter.decodeBase64BToFile(base64Keystore, tmpPath + File.separator + "user.jks");
out.println(new String(decoded));
}
cloudStormCredentialInfo.setUserKeyName("user.jks"); cloudStormCredentialInfo.setUserKeyName("user.jks");
cloudStormCredentialInfo.setKeyAlias(toscaCredentials.getUser()); cloudStormCredentialInfo.setKeyAlias(toscaCredentials.getUser());
cloudStormCredentialInfo.setKeyPassword(toscaCredentials.getToken()); cloudStormCredentialInfo.setKeyPassword(toscaCredentials.getToken());
......
...@@ -101,9 +101,7 @@ def save(file): ...@@ -101,9 +101,7 @@ def save(file):
# try: # try:
# tosca_template_file_path = os.path.join(db_dir_path, file.filename) # tosca_template_file_path = os.path.join(db_dir_path, file.filename)
start = time.time() start = time.time()
logger.info("Got request for tosca template") logger.info("Got request for tosca template")
purge_all_tables() purge_all_tables()
dictionary = yaml.safe_load(file.stream) dictionary = yaml.safe_load(file.stream)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment