This is an automated email from the ASF dual-hosted git repository.
rohit pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/main by this push:
new 161d156af1a Support user resource name / displaytext with emoji,
unicode chars, and some sql exception msg improvements (#9340)
161d156af1a is described below
commit 161d156af1a5bc8ee2c88de301daadbade7d8f3e
Author: Suresh Kumar Anaparti <[email protected]>
AuthorDate: Wed Aug 7 14:50:29 2024 +0530
Support user resource name / displaytext with emoji, unicode chars, and
some sql exception msg improvements (#9340)
* Added API arg validator for RFC compliance domain name, to validate VM's
host name
* Added unit tests for vm host/domain name validation
* Don't send sql exception/query from dao to upper layer, log it and send
only the error message
* Updated user resources name / displayname(/text) column's charset to
utf8mb4 to support emojis / unicode chars
* Check and update char set for affinity group name to utf8mb4, from the
data migration in upgrade path
* Added smoke test to check resource name for vm, volume, service & disk
offering, template, iso, account(first/lastname)
* Updated resource annotation charset to utf8mb4
* Updated some resources description charset to utf8mb4
* Updated sql stmt with constant
* Updated modify columns char set with idempotent procedure
* Removed delimiter (for creating procedures)
---
.../org/apache/cloudstack/api/ApiArgValidator.java | 5 +
.../api/command/user/vm/DeployVMCmd.java | 3 +-
.../api/command/user/vm/UpdateVMCmd.java | 4 +-
.../com/cloud/upgrade/dao/Upgrade41910to42000.java | 33 +++
.../resources/META-INF/db/schema-41910to42000.sql | 72 ++++-
.../java/com/cloud/utils/db/GenericDaoBase.java | 65 +++--
.../com/cloud/api/dispatch/ParamProcessWorker.java | 43 ++-
.../cloud/api/dispatch/ParamProcessWorkerTest.java | 37 +++
test/integration/smoke/test_resource_names.py | 299 +++++++++++++++++++++
9 files changed, 526 insertions(+), 35 deletions(-)
diff --git a/api/src/main/java/org/apache/cloudstack/api/ApiArgValidator.java
b/api/src/main/java/org/apache/cloudstack/api/ApiArgValidator.java
index 3e06fc0e44e..38047235273 100644
--- a/api/src/main/java/org/apache/cloudstack/api/ApiArgValidator.java
+++ b/api/src/main/java/org/apache/cloudstack/api/ApiArgValidator.java
@@ -32,4 +32,9 @@ public enum ApiArgValidator {
* Validates if the parameter is an UUID with the method {@link
UuidUtils#isUuid(String)}.
*/
UuidString,
+
+ /**
+ * Validates if the parameter is a valid RFC Compliance domain name.
+ */
+ RFCComplianceDomainName,
}
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java
index 68dd49d9ce5..52d42a95d98 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/DeployVMCmd.java
@@ -31,6 +31,7 @@ import org.apache.cloudstack.acl.RoleType;
import org.apache.cloudstack.affinity.AffinityGroupResponse;
import org.apache.cloudstack.api.ACL;
import org.apache.cloudstack.api.APICommand;
+import org.apache.cloudstack.api.ApiArgValidator;
import org.apache.cloudstack.api.ApiCommandResourceType;
import org.apache.cloudstack.api.ApiConstants;
import org.apache.cloudstack.api.ApiConstants.IoDriverPolicy;
@@ -97,7 +98,7 @@ public class DeployVMCmd extends BaseAsyncCreateCustomIdCmd
implements SecurityG
@Parameter(name = ApiConstants.TEMPLATE_ID, type = CommandType.UUID,
entityType = TemplateResponse.class, required = true, description = "the ID of
the template for the virtual machine")
private Long templateId;
- @Parameter(name = ApiConstants.NAME, type = CommandType.STRING,
description = "host name for the virtual machine")
+ @Parameter(name = ApiConstants.NAME, type = CommandType.STRING,
description = "host name for the virtual machine", validations =
{ApiArgValidator.RFCComplianceDomainName})
private String name;
@Parameter(name = ApiConstants.DISPLAY_NAME, type = CommandType.STRING,
description = "an optional user generated name for the virtual machine")
diff --git
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
index 024337356fd..110b2d2255d 100644
---
a/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
+++
b/api/src/main/java/org/apache/cloudstack/api/command/user/vm/UpdateVMCmd.java
@@ -22,6 +22,8 @@ import java.util.List;
import java.util.Map;
import com.cloud.utils.exception.CloudRuntimeException;
+
+import org.apache.cloudstack.api.ApiArgValidator;
import org.apache.cloudstack.api.response.UserDataResponse;
import org.apache.cloudstack.acl.RoleType;
@@ -104,7 +106,7 @@ public class UpdateVMCmd extends BaseCustomIdCmd implements
SecurityGroupAction,
description = "true if VM contains XS/VMWare tools inorder to
support dynamic scaling of VM cpu/memory. This can be updated only when dynamic
scaling is enabled on template, service offering and the corresponding global
setting")
protected Boolean isDynamicallyScalable;
- @Parameter(name = ApiConstants.NAME, type = CommandType.STRING,
description = "new host name of the vm. The VM has to be stopped/started for
this update to take affect", since = "4.4")
+ @Parameter(name = ApiConstants.NAME, type = CommandType.STRING,
description = "new host name of the vm. The VM has to be stopped/started for
this update to take affect", validations =
{ApiArgValidator.RFCComplianceDomainName}, since = "4.4")
private String name;
@Parameter(name = ApiConstants.INSTANCE_NAME, type = CommandType.STRING,
description = "instance name of the user vm", since = "4.4", authorized =
{RoleType.Admin})
diff --git
a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to42000.java
b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to42000.java
index d8c7684f8e3..4c26c6bde50 100644
--- a/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to42000.java
+++ b/engine/schema/src/main/java/com/cloud/upgrade/dao/Upgrade41910to42000.java
@@ -18,12 +18,16 @@ package com.cloud.upgrade.dao;
import java.io.InputStream;
import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
import com.cloud.upgrade.SystemVmTemplateRegistration;
import com.cloud.utils.exception.CloudRuntimeException;
public class Upgrade41910to42000 extends DbUpgradeAbstractImpl implements
DbUpgrade, DbUpgradeSystemVmTemplate {
private SystemVmTemplateRegistration systemVmTemplateRegistration;
+ private static final int MAX_INDEXED_CHARS_IN_CHAR_SET_UTF8MB4 = 191;
@Override
public String[] getUpgradableVersionRange() {
@@ -53,6 +57,7 @@ public class Upgrade41910to42000 extends
DbUpgradeAbstractImpl implements DbUpgr
@Override
public void performDataMigration(Connection conn) {
+ checkAndUpdateAffinityGroupNameCharSetToUtf8mb4(conn);
}
@Override
@@ -80,4 +85,32 @@ public class Upgrade41910to42000 extends
DbUpgradeAbstractImpl implements DbUpgr
throw new CloudRuntimeException("Failed to find / register
SystemVM template(s)");
}
}
+
+ private void checkAndUpdateAffinityGroupNameCharSetToUtf8mb4(Connection
conn) {
+ logger.debug("Check and update char set for affinity group name to
utf8mb4");
+ try {
+ PreparedStatement pstmt = conn.prepareStatement("SELECT
MAX(LENGTH(name)) FROM `cloud`.`affinity_group`");
+ ResultSet rs = pstmt.executeQuery();
+ if (rs.next()) {
+ long maxLengthOfName = rs.getLong(1);
+ if (maxLengthOfName <= MAX_INDEXED_CHARS_IN_CHAR_SET_UTF8MB4) {
+ pstmt = conn.prepareStatement(String.format("ALTER TABLE
`cloud`.`affinity_group` MODIFY `name` VARCHAR(%d) CHARACTER SET utf8mb4 NOT
NULL", MAX_INDEXED_CHARS_IN_CHAR_SET_UTF8MB4));
+ pstmt.executeUpdate();
+ logger.debug("Successfully updated char set for affinity
group name to utf8mb4");
+ } else {
+ logger.warn("Unable to update char set for affinity group
name, as there are some names with more than " +
MAX_INDEXED_CHARS_IN_CHAR_SET_UTF8MB4 +
+ " chars (max supported chars for index)");
+ }
+ }
+
+ if (rs != null && !rs.isClosed()) {
+ rs.close();
+ }
+ if (pstmt != null && !pstmt.isClosed()) {
+ pstmt.close();
+ }
+ } catch (final SQLException e) {
+ logger.warn("Exception while updating char set for affinity group
name to utf8mb4: " + e.getMessage());
+ }
+ }
}
diff --git
a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql
b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql
index f59eda5c06c..c522b45e282 100644
--- a/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql
+++ b/engine/schema/src/main/resources/META-INF/db/schema-41910to42000.sql
@@ -151,6 +151,76 @@ WHERE
name IN ("quota.usage.smtp.useStartTLS", "quota.usage.smtp.useAuth",
"alert.smtp.useAuth", "project.smtp.useAuth")
AND value NOT IN ("true", "y", "t", "1", "on", "yes");
-
-- Quota inject tariff result into subsequent ones
CALL `cloud_usage`.`IDEMPOTENT_ADD_COLUMN`('cloud_usage.quota_tariff',
'position', 'bigint(20) NOT NULL DEFAULT 1 COMMENT "Position in the execution
sequence for tariffs of the same type"');
+
+-- Idempotent IDEMPOTENT_MODIFY_COLUMN_CHAR_SET
+DROP PROCEDURE IF EXISTS `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`;
+CREATE PROCEDURE `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET` (
+ IN in_table_name VARCHAR(200)
+, IN in_column_name VARCHAR(200)
+, IN in_column_type VARCHAR(200)
+, IN in_column_definition VARCHAR(1000)
+)
+BEGIN
+ DECLARE CONTINUE HANDLER FOR 1060 BEGIN END; SET @ddl = CONCAT('ALTER
TABLE ', in_table_name); SET @ddl = CONCAT(@ddl, ' ', ' MODIFY COLUMN') ; SET
@ddl = CONCAT(@ddl, ' ', in_column_name); SET @ddl = CONCAT(@ddl, ' ',
in_column_type); SET @ddl = CONCAT(@ddl, ' ', ' CHARACTER SET utf8mb4'); SET
@ddl = CONCAT(@ddl, ' ', in_column_definition); PREPARE stmt FROM @ddl; EXECUTE
stmt; DEALLOCATE PREPARE stmt; END;
+
+DROP PROCEDURE IF EXISTS `cloud_usage`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`;
+CREATE PROCEDURE `cloud_usage`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET` (
+ IN in_table_name VARCHAR(200)
+, IN in_column_name VARCHAR(200)
+, IN in_column_type VARCHAR(200)
+, IN in_column_definition VARCHAR(1000)
+)
+BEGIN
+ DECLARE CONTINUE HANDLER FOR 1060 BEGIN END; SET @ddl = CONCAT('ALTER
TABLE ', in_table_name); SET @ddl = CONCAT(@ddl, ' ', ' MODIFY COLUMN') ; SET
@ddl = CONCAT(@ddl, ' ', in_column_name); SET @ddl = CONCAT(@ddl, ' ',
in_column_type); SET @ddl = CONCAT(@ddl, ' ', ' CHARACTER SET utf8mb4'); SET
@ddl = CONCAT(@ddl, ' ', in_column_definition); PREPARE stmt FROM @ddl; EXECUTE
stmt; DEALLOCATE PREPARE stmt; END;
+
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('async_job', 'job_result',
'TEXT', 'COMMENT \'job result info\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('async_job', 'job_cmd_info',
'TEXT', 'COMMENT \'command parameter info\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('event', 'description',
'VARCHAR(1024)', 'NOT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('usage_event',
'resource_name', 'VARCHAR(255)', 'DEFAULT NULL');
+CALL `cloud_usage`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('usage_event',
'resource_name', 'VARCHAR(255)', 'DEFAULT NULL');
+
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('account', 'account_name',
'VARCHAR(100)', 'DEFAULT NULL COMMENT \'an account name set by the creator of
the account, defaults to username for single accounts\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('affinity_group',
'description', 'VARCHAR(4096)', 'DEFAULT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('annotations', 'annotation',
'TEXT', '');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('autoscale_vmgroups', 'name',
'VARCHAR(255)', 'DEFAULT NULL COMMENT \'name of the autoscale vm group\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('backup_offering', 'name',
'VARCHAR(255)', 'NOT NULL COMMENT \'backup offering name\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('backup_offering',
'description', 'VARCHAR(255)', 'NOT NULL COMMENT \'backup offering
description\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('disk_offering', 'name',
'VARCHAR(255)', 'NOT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('disk_offering',
'unique_name', 'VARCHAR(32)', 'DEFAULT NULL COMMENT \'unique name\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('disk_offering',
'display_text', 'VARCHAR(4096)', 'DEFAULT NULL COMMENT \'Optional text set by
the admin for display purpose only\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('instance_group', 'name',
'VARCHAR(255)', 'NOT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('kubernetes_cluster', 'name',
'VARCHAR(255)', 'NOT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('kubernetes_cluster',
'description', 'VARCHAR(4096)', 'DEFAULT NULL COMMENT \'display text for this
Kubernetes cluster\'');
+CALL
`cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('kubernetes_supported_version',
'name', 'VARCHAR(255)', 'NOT NULL COMMENT \'the name of this Kubernetes
version\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('network_offerings', 'name',
'VARCHAR(64)', 'DEFAULT NULL COMMENT \'name of the network offering\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('network_offerings',
'unique_name', 'VARCHAR(64)', 'DEFAULT NULL COMMENT \'unique name of the
network offering\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('network_offerings',
'display_text', 'VARCHAR(255)', 'NOT NULL COMMENT \'text to display to
users\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('networks', 'name',
'VARCHAR(255)', 'DEFAULT NULL COMMENT \'name for this network\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('networks', 'display_text',
'VARCHAR(255)', 'DEFAULT NULL COMMENT \'display text for this network\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('project_role',
'description', 'TEXT', 'COMMENT \'description of the project role\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('projects', 'name',
'VARCHAR(255)', 'DEFAULT NULL COMMENT \'project name\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('projects', 'display_text',
'VARCHAR(255)', 'DEFAULT NULL COMMENT \'project display text\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('roles', 'description',
'TEXT', 'COMMENT \'description of the role\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('service_offering', 'name',
'VARCHAR(255)', 'NOT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('service_offering',
'unique_name', 'VARCHAR(32)', 'DEFAULT NULL COMMENT \'unique name for
offerings\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('service_offering',
'display_text', 'VARCHAR(4096)', 'DEFAULT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('snapshots', 'name',
'VARCHAR(255)', 'NOT NULL COMMENT \'snapshot name\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('ssh_keypairs',
'keypair_name', 'VARCHAR(256)', 'NOT NULL COMMENT \'name of the key pair\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('user_vm', 'display_name',
'VARCHAR(255)', 'DEFAULT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('user_vm_details', 'value',
'VARCHAR(5120)', 'NOT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('user', 'firstname',
'VARCHAR(255)', 'DEFAULT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('user', 'lastname',
'VARCHAR(255)', 'DEFAULT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('user_data', 'name',
'VARCHAR(256)', 'NOT NULL COMMENT \'name of the user data\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('vm_instance',
'display_name', 'VARCHAR(255)', 'DEFAULT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('vm_snapshots',
'display_name', 'VARCHAR(255)', 'DEFAULT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('vm_snapshots',
'description', 'VARCHAR(255)', 'DEFAULT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('vm_template', 'name',
'VARCHAR(255)', 'NOT NULL');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('vm_template',
'display_text', 'VARCHAR(4096)', 'DEFAULT NULL COMMENT \'Description text set
by the admin for display purpose only\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('volumes', 'name',
'VARCHAR(255)', 'DEFAULT NULL COMMENT \'A user specified name for the
volume\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('vpc', 'name',
'VARCHAR(255)', 'DEFAULT NULL COMMENT \'vpc name\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('vpc', 'display_text',
'VARCHAR(255)', 'DEFAULT NULL COMMENT \'vpc display text\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('vpc_offerings', 'name',
'VARCHAR(255)', 'DEFAULT NULL COMMENT \'vpc offering name\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('vpc_offerings',
'unique_name', 'VARCHAR(64)', 'DEFAULT NULL COMMENT \'unique name of the vpc
offering\'');
+CALL `cloud`.`IDEMPOTENT_MODIFY_COLUMN_CHAR_SET`('vpc_offerings',
'display_text', 'VARCHAR(255)', 'DEFAULT NULL COMMENT \'display text\'');
diff --git a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java
b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java
index b7e4f44cf8c..52a6b204ee8 100644
--- a/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java
+++ b/framework/db/src/main/java/com/cloud/utils/db/GenericDaoBase.java
@@ -437,9 +437,11 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
}
return result;
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to find on DB, due to: " +
e.getLocalizedMessage());
} catch (final Exception e) {
- throw new CloudRuntimeException("Caught: " + pstmt, e);
+ logger.error("Caught: " + pstmt, e);
+ throw new CloudRuntimeException("Caught error: " +
e.getLocalizedMessage());
}
}
@@ -522,9 +524,11 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
return results;
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to find on DB, due to: " +
e.getLocalizedMessage());
} catch (final Exception e) {
- throw new CloudRuntimeException("Caught: " + pstmt, e);
+ logger.error("Caught: " + pstmt, e);
+ throw new CloudRuntimeException("Caught error: " +
e.getLocalizedMessage());
}
}
@@ -870,8 +874,9 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
ub.clear();
return result;
} catch (final SQLException e) {
+ logger.error("DB Exception on: " + pstmt, e);
handleEntityExistsException(e);
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to update on DB, due to: "
+ e.getLocalizedMessage());
}
}
@@ -1065,7 +1070,8 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
ResultSet rs = pstmt.executeQuery();
return rs.next() ? toEntityBean(rs, true) : null;
} catch (SQLException e) {
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to find by id on DB, due
to: " + e.getLocalizedMessage());
}
}
@@ -1180,9 +1186,11 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
}
return result;
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to execute on DB, due to:
" + e.getLocalizedMessage());
} catch (final Exception e) {
- throw new CloudRuntimeException("Caught: " + pstmt, e);
+ logger.error("Caught: " + pstmt, e);
+ throw new CloudRuntimeException("Caught error: " +
e.getLocalizedMessage());
}
}
@@ -1231,7 +1239,8 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
}
return true;
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to expunge on DB, due to:
" + e.getLocalizedMessage());
}
}
@@ -1269,9 +1278,11 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
}
return pstmt.executeUpdate();
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to expunge on DB, due to:
" + e.getLocalizedMessage());
} catch (final Exception e) {
- throw new CloudRuntimeException("Caught: " + pstmt, e);
+ logger.error("Caught: " + pstmt, e);
+ throw new CloudRuntimeException("Caught error: " +
e.getLocalizedMessage());
}
}
@Override
@@ -1550,7 +1561,7 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
return entity;
}
- assert false : "Can't call persit if you don't have primary key";
+ assert false : "Can't call persist if you don't have primary key";
}
ID id = null;
@@ -1606,8 +1617,9 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
}
txn.commit();
} catch (final SQLException e) {
+ logger.error("DB Exception on: " + pstmt, e);
handleEntityExistsException(e);
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to persist on DB, due to:
" + e.getLocalizedMessage());
} catch (IllegalArgumentException e) {
throw new CloudRuntimeException("Problem with getting the ec
attribute ", e);
} catch (IllegalAccessException e) {
@@ -1956,7 +1968,8 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
pstmt.executeUpdate();
txn.commit();
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception on " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to expunge on DB, due to:
" + e.getLocalizedMessage());
}
}
@@ -1984,7 +1997,8 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
}
return result > 0;
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to unremove on DB, due to:
" + e.getLocalizedMessage());
}
}
@@ -2027,7 +2041,8 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
}
return result > 0;
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to remove on DB, due to: "
+ e.getLocalizedMessage());
}
}
@@ -2175,9 +2190,11 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
}
return 0;
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to get count on DB, due
to: " + e.getLocalizedMessage());
} catch (final Exception e) {
- throw new CloudRuntimeException("Caught: " + pstmt, e);
+ logger.error("Caught: " + pstmt, e);
+ throw new CloudRuntimeException("Caught error: " +
e.getLocalizedMessage());
}
}
@@ -2234,9 +2251,11 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
}
return 0;
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception in executing: " +
sql, e);
+ logger.error("DB Exception in executing: " + sql, e);
+ throw new CloudRuntimeException("Unable to get count on DB, due
to: " + e.getLocalizedMessage());
} catch (final Exception e) {
- throw new CloudRuntimeException("Caught exception in : " + sql, e);
+ logger.error("Caught exception in : " + sql, e);
+ throw new CloudRuntimeException("Caught error: " +
e.getLocalizedMessage());
}
}
@@ -2309,9 +2328,11 @@ public abstract class GenericDaoBase<T, ID extends
Serializable> extends Compone
}
return 0;
} catch (final SQLException e) {
- throw new CloudRuntimeException("DB Exception on: " + pstmt, e);
+ logger.error("DB Exception on: " + pstmt, e);
+ throw new CloudRuntimeException("Unable to get count on DB, due
to: " + e.getLocalizedMessage());
} catch (final Exception e) {
- throw new CloudRuntimeException("Caught: " + pstmt, e);
+ logger.error("Caught: " + pstmt, e);
+ throw new CloudRuntimeException("Caught error: " +
e.getLocalizedMessage());
}
}
diff --git
a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java
b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java
index bdba8dcace2..314b83acdb5 100644
--- a/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java
+++ b/server/src/main/java/com/cloud/api/dispatch/ParamProcessWorker.java
@@ -60,6 +60,7 @@ import com.cloud.utils.DateUtil;
import com.cloud.utils.UuidUtils;
import com.cloud.utils.db.EntityManager;
import com.cloud.utils.exception.CloudRuntimeException;
+import com.cloud.utils.net.NetUtils;
public class ParamProcessWorker implements DispatchWorker {
@@ -117,8 +118,21 @@ public class ParamProcessWorker implements DispatchWorker {
}
}
+ private void validateNameForRFCCompliance(final Object param, final String
argName) {
+ String value = String.valueOf(param);
+ if (StringUtils.isBlank(value) ||
!NetUtils.verifyDomainNameLabel(value, true)) {
+ String msg = "it can contain ASCII letters 'a' through 'z', the
digits '0' through '9', "
+ + "and the hyphen ('-'), must be between 1 and 63
characters long, and can't start or end with \"-\" and can't start with digit";
+ throwInvalidParameterValueException(argName, msg);
+ }
+ }
+
protected void throwInvalidParameterValueException(String argName) {
- throw new InvalidParameterValueException(String.format("Invalid value
provided for API arg: %s", argName));
+ throwInvalidParameterValueException(argName, null);
+ }
+
+ protected void throwInvalidParameterValueException(String argName, String
customMsg) {
+ throw new InvalidParameterValueException(String.format("Invalid value
provided for API arg: %s%s", argName, StringUtils.isBlank(customMsg)? "" : " -
" + customMsg));
}
private void validateField(final Object paramObj, final Parameter
annotation) throws ServerApiException {
@@ -155,6 +169,12 @@ public class ParamProcessWorker implements DispatchWorker {
break;
}
break;
+ case RFCComplianceDomainName:
+ switch (annotation.type()) {
+ case STRING:
+ validateNameForRFCCompliance(paramObj, argName);
+ break;
+ }
}
}
}
@@ -165,14 +185,18 @@ public class ParamProcessWorker implements DispatchWorker
{
final List<Field> cmdFields = cmd.getParamFields();
+ String commandName = cmd.getCommandName();
+ if (commandName.endsWith(BaseCmd.RESPONSE_SUFFIX)) {
+ commandName = cmd.getCommandName().substring(0,
cmd.getCommandName().length() - 8);
+ }
+
for (final Field field : cmdFields) {
final Parameter parameterAnnotation =
field.getAnnotation(Parameter.class);
final Object paramObj = params.get(parameterAnnotation.name());
if (paramObj == null) {
if (parameterAnnotation.required()) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR,
"Unable to execute API command " +
- cmd.getCommandName().substring(0,
cmd.getCommandName().length() - 8) +
- " due to missing parameter " +
parameterAnnotation.name());
+ commandName + " due to missing parameter " +
parameterAnnotation.name());
}
continue;
}
@@ -186,29 +210,28 @@ public class ParamProcessWorker implements DispatchWorker
{
setFieldValue(field, cmd, paramObj, parameterAnnotation);
} catch (final IllegalArgumentException argEx) {
if (logger.isDebugEnabled()) {
- logger.debug("Unable to execute API command " +
cmd.getCommandName() + " due to invalid value " + paramObj + " for parameter " +
+ logger.debug("Unable to execute API command " +
commandName + " due to invalid value " + paramObj + " for parameter " +
parameterAnnotation.name());
}
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable
to execute API command " +
- cmd.getCommandName().substring(0,
cmd.getCommandName().length() - 8) + " due to invalid value " + paramObj + "
for parameter " +
+ commandName + " due to invalid value " + paramObj + "
for parameter " +
parameterAnnotation.name());
} catch (final ParseException parseEx) {
if (logger.isDebugEnabled()) {
- logger.debug("Invalid date parameter " + paramObj + "
passed to command " + cmd.getCommandName().substring(0,
cmd.getCommandName().length() - 8));
+ logger.debug("Invalid date parameter " + paramObj + "
passed to command " + commandName);
}
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable
to parse date " + paramObj + " for command " +
- cmd.getCommandName().substring(0,
cmd.getCommandName().length() - 8) + ", please pass dates in the format
mentioned in the api documentation");
+ commandName + ", please pass dates in the format
mentioned in the api documentation");
} catch (final InvalidParameterValueException invEx) {
throw new ServerApiException(ApiErrorCode.PARAM_ERROR, "Unable
to execute API command " +
- cmd.getCommandName().substring(0,
cmd.getCommandName().length() - 8) + " due to invalid value. " +
invEx.getMessage());
+ commandName + " due to invalid value. " +
invEx.getMessage());
} catch (final CloudRuntimeException cloudEx) {
logger.error("CloudRuntimeException", cloudEx);
// FIXME: Better error message? This only happens if the API
command is not executable, which typically
//means
// there was
// and IllegalAccessException setting one of the parameters.
- throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
"Internal error executing API command " +
- cmd.getCommandName().substring(0,
cmd.getCommandName().length() - 8));
+ throw new ServerApiException(ApiErrorCode.INTERNAL_ERROR,
"Internal error executing API command " + commandName);
}
//check access on the resource this field points to
diff --git
a/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java
b/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java
index 22c0ba5a795..da70bc1c1bf 100644
--- a/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java
+++ b/server/src/test/java/com/cloud/api/dispatch/ParamProcessWorkerTest.java
@@ -26,6 +26,7 @@ import com.cloud.exception.ResourceUnavailableException;
import com.cloud.user.Account;
import com.cloud.user.AccountManager;
import com.cloud.user.User;
+import org.apache.cloudstack.api.ApiArgValidator;
import org.apache.cloudstack.api.BaseCmd;
import org.apache.cloudstack.api.Parameter;
import org.apache.cloudstack.api.ServerApiException;
@@ -63,6 +64,9 @@ public class ParamProcessWorkerTest {
@Parameter(name = "doubleparam1", type = CommandType.DOUBLE)
double doubleparam1;
+ @Parameter(name = "vmHostNameParam", type = CommandType.STRING,
validations = {ApiArgValidator.RFCComplianceDomainName})
+ String vmHostNameParam;
+
@Override
public void execute() throws ResourceUnavailableException,
InsufficientCapacityException, ServerApiException, ConcurrentOperationException,
ResourceAllocationException, NetworkRuleConflictException {
@@ -100,11 +104,44 @@ public class ParamProcessWorkerTest {
params.put("intparam1", "100");
params.put("boolparam1", "true");
params.put("doubleparam1", "11.89");
+ params.put("vmHostNameParam", "test-host-name-123");
final TestCmd cmd = new TestCmd();
paramProcessWorker.processParameters(cmd, params);
Assert.assertEquals("foo", cmd.strparam1);
Assert.assertEquals(100, cmd.intparam1);
Assert.assertTrue(Double.compare(cmd.doubleparam1, 11.89) == 0);
+ Assert.assertEquals("test-host-name-123", cmd.vmHostNameParam);
+ }
+
+ @Test(expected = ServerApiException.class)
+ public void processVmHostNameParameter_CannotStartWithDigit() {
+ final HashMap<String, String> params = new HashMap<String, String>();
+ params.put("vmHostNameParam", "123test");
+ final TestCmd cmd = new TestCmd();
+ paramProcessWorker.processParameters(cmd, params);
+ }
+
+ @Test(expected = ServerApiException.class)
+ public void processVmHostNameParameter_CannotStartWithHypen() {
+ final HashMap<String, String> params = new HashMap<String, String>();
+ params.put("vmHostNameParam", "-test");
+ final TestCmd cmd = new TestCmd();
+ paramProcessWorker.processParameters(cmd, params);
+ }
+
+ @Test(expected = ServerApiException.class)
+ public void processVmHostNameParameter_CannotEndWithHypen() {
+ final HashMap<String, String> params = new HashMap<String, String>();
+ params.put("vmHostNameParam", "test-");
+ final TestCmd cmd = new TestCmd();
+ paramProcessWorker.processParameters(cmd, params);
}
+ @Test(expected = ServerApiException.class)
+ public void processVmHostNameParameter_NotMoreThan63Chars() {
+ final HashMap<String, String> params = new HashMap<String, String>();
+ params.put("vmHostNameParam",
"test-f2405112-d5a1-47c1-9f00-976909e3a6d3-1e6f3264-955ee76011a99");
+ final TestCmd cmd = new TestCmd();
+ paramProcessWorker.processParameters(cmd, params);
+ }
}
diff --git a/test/integration/smoke/test_resource_names.py
b/test/integration/smoke/test_resource_names.py
new file mode 100644
index 00000000000..46fa445f1b1
--- /dev/null
+++ b/test/integration/smoke/test_resource_names.py
@@ -0,0 +1,299 @@
+# -- coding: utf-8 --
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+""" BVT tests for resource names with emojis / unicode
+"""
+from marvin.cloudstackTestCase import cloudstackTestCase
+
+from marvin.lib.base import (Account,
+ ServiceOffering,
+ VirtualMachine,
+ Template,
+ Iso,
+ Volume,
+ DiskOffering)
+from marvin.lib.common import (get_domain,
+ get_zone,
+ get_suitable_test_template,
+ get_builtin_template_info)
+from marvin.codes import FAILED
+from nose.plugins.attrib import attr
+# Import System modules
+import time
+
+_multiprocess_shared_ = True
+
+class TestResourceNames(cloudstackTestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ testClient = super(TestResourceNames, cls).getClsTestClient()
+ cls.apiclient = testClient.getApiClient()
+ cls.services = testClient.getParsedTestDataConfig()
+ # Get Zone, Domain and templates
+ cls.domain = get_domain(cls.apiclient)
+ cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
+ cls.hypervisor = testClient.getHypervisorInfo()
+ cls.services['mode'] = cls.zone.networktype
+ cls._cleanup = []
+
+ template = get_suitable_test_template(
+ cls.apiclient,
+ cls.zone.id,
+ cls.services["ostype"],
+ cls.hypervisor
+ )
+ if template == FAILED:
+ assert False, "get_suitable_test_template() failed to return
template with description %s" % cls.services[
+ "ostype"]
+
+ # Set Zones and disk offerings
+ cls.services["domainid"] = cls.domain.id
+ cls.services["zoneid"] = cls.zone.id
+ cls.services["template"] = template.id
+ cls.services["iso1"]["zoneid"] = cls.zone.id
+ cls.services["small"]["zoneid"] = cls.zone.id
+ cls.services["small"]["template"] = template.id
+
+ cls.services["account"]["firstname"] = "test🎉"
+ cls.services["account"]["lastname"] = "account🙂"
+ cls.account = Account.create(
+ cls.apiclient,
+ cls.services["account"],
+ domainid=cls.domain.id
+ )
+ cls._cleanup.append(cls.account)
+
+ cls.services["service_offerings"]["tiny"]["name"] = "test🎉svcoffering🙂"
+ cls.service_offering = ServiceOffering.create(
+ cls.apiclient,
+ cls.services["service_offerings"]["tiny"]
+ )
+ cls._cleanup.append(cls.service_offering)
+
+ cls.services["disk_offering"]["name"] = "test🎉diskoffering🙂"
+ cls.disk_offering = DiskOffering.create(
+ cls.apiclient,
+ cls.services["disk_offering"]
+ )
+ cls._cleanup.append(cls.disk_offering)
+
+ cls.services["small"]["displayname"] = "test🎉vm🙂"
+ cls.virtual_machine = VirtualMachine.create(
+ cls.apiclient,
+ cls.services["small"],
+ accountid=cls.account.name,
+ domainid=cls.account.domainid,
+ serviceofferingid=cls.service_offering.id,
+ mode=cls.services['mode']
+ )
+
+ @classmethod
+ def tearDownClass(cls):
+ super(TestResourceNames, cls).tearDownClass()
+
+ def setUp(self):
+ self.apiclient = self.testClient.getApiClient()
+ self.dbclient = self.testClient.getDbConnection()
+ self.cleanup = []
+
+ def tearDown(self):
+ super(TestResourceNames, self).tearDown()
+
+ @attr(tags=["advanced", "smoke", "basic"], required_hardware="false")
+ def test_01_deploy_vm(self):
+ """Test for deploy virtual machine
+ """
+ # Validate the following:
+ # 1. listVirtualMachines returns accurate information, and check name
+ list_vm_response = VirtualMachine.list(
+ self.apiclient,
+ id=self.virtual_machine.id
+ )
+
+ self.debug(
+ "Verify listVirtualMachines response for virtual machine: %s" \
+ % self.virtual_machine.id
+ )
+ self.assertEqual(
+ isinstance(list_vm_response, list),
+ True,
+ "Check list response returns a valid list"
+ )
+ self.assertNotEqual(
+ len(list_vm_response),
+ 0,
+ "Check VM available in List Virtual Machines"
+ )
+
+ vm_response = list_vm_response[0]
+ self.assertEqual(
+ vm_response.id,
+ self.virtual_machine.id,
+ "Check virtual machine id in listVirtualMachines"
+ )
+ self.assertEqual(
+ vm_response.name,
+ self.virtual_machine.name,
+ "Check virtual machine name in listVirtualMachines"
+ )
+ self.assertEqual(
+ vm_response.displayname,
+ self.virtual_machine.displayname,
+ "Check virtual machine display name in listVirtualMachines"
+ )
+ self.assertEqual(
+ vm_response.state,
+ 'Running',
+ msg="VM is not in Running state"
+ )
+ return
+
+ @attr(tags=["advanced", "smoke", "basic"], required_hardware="true")
+ def test_02_create_volume(self):
+ """Test for create volume
+ """
+ # Validate the following:
+ # 1. Create volume and check name
+
+ self.services["diskname"] = "test🎉data🙂volume"
+ self.volume = Volume.create(
+ self.apiclient,
+ self.services,
+ account=self.account.name,
+ domainid=self.account.domainid,
+ diskofferingid=self.disk_offering.id
+ )
+ # self.cleanup.append(self.volume)
+ self.virtual_machine.attach_volume(self.apiclient, self.volume)
+ list_volume_response = Volume.list(
+ self.apiclient,
+ id=self.volume.id
+ )
+ self.assertEqual(
+ isinstance(list_volume_response, list),
+ True,
+ "Check list response returns a valid list"
+ )
+ self.assertNotEqual(
+ list_volume_response,
+ None,
+ "Check if volume exists in ListVolumes"
+ )
+
+ volume_response = list_volume_response[0]
+ self.assertNotEqual(
+ volume_response.virtualmachineid,
+ None,
+ "Check if volume state (attached) is reflected"
+ )
+ self.assertEqual(
+ volume_response.name,
+ self.volume.name,
+ "Check virtual machine display name in listVirtualMachines"
+ )
+
+ @attr(tags=["advanced", "smoke", "basic"], required_hardware="true")
+ def test_03_register_template(self):
+ """Test for register template
+ """
+ # Validate the following:
+ # 1. Register template and check name
+
+ if self.hypervisor.lower() in ["lxc"]:
+ self.skipTest("Skipping test, unsupported hypervisor %s" %
self.hypervisor)
+
+ builtin_info = get_builtin_template_info(self.apiclient, self.zone.id)
+ self.services["template_2"]["url"] = builtin_info[0]
+ self.services["template_2"]["hypervisor"] = builtin_info[1]
+ self.services["template_2"]["format"] = builtin_info[2]
+ self.services["template_2"]["name"] = "test🎉tmpl🙂"
+ self.services["template_2"]["displaytext"] = "test🎉tmpl🙂"
+
+ template = Template.register(self.apiclient,
+ self.services["template_2"],
+ zoneid=self.zone.id,
+ account=self.account.name,
+ domainid=self.account.domainid
+ )
+ self.debug("Successfully registered template with ID: %s" %
template.id)
+ self.cleanup.append(template)
+
+ # Get template response
+ timeout = 600
+ list_template_response = None
+ while timeout >= 0:
+ list_template_response = Template.list(self.apiclient,
+
templatefilter=self.services["template_2"]["templatefilter"],
+ id=template.id)
+
+ if list_template_response is not None and
list_template_response[0].isready:
+ break
+
+ time.sleep(30)
+ timeout -= 30
+
+ template_response = list_template_response[0]
+ self.assertEqual(
+ template_response.displaytext,
+ template.displaytext,
+ "Check template displaytext in response"
+ )
+
+ @attr(tags=["advanced", "smoke", "basic"], required_hardware="true")
+ def test_04_register_iso(self):
+ """Test for register ISO
+ """
+ # Validate the following:
+ # 1. Register ISO and check name
+
+ if self.hypervisor.lower() in ["lxc"]:
+ self.skipTest("Skipping test, unsupported hypervisor %s" %
self.hypervisor)
+
+ self.services["iso1"]["displaytext"] = "test🎉iso🙂"
+ self.services["iso1"]["name"] = "test🎉iso🙂"
+ iso = Iso.create(
+ self.apiclient,
+ self.services["iso1"],
+ account=self.account.name,
+ domainid=self.account.domainid
+ )
+ self.debug("Successfully registered ISO with ID: %s" % iso.id)
+ self.cleanup.append(iso)
+
+ # Get ISO response
+ timeout = 600
+ list_iso_response = None
+ while timeout >= 0:
+ list_iso_response = Iso.list(
+ self.apiclient,
+ isofilter="self",
+ id=iso.id
+ )
+
+ if list_iso_response is not None and list_iso_response[0].isready:
+ break
+
+ time.sleep(30)
+ timeout -= 30
+
+ iso_response = list_iso_response[0]
+ self.assertEqual(
+ iso_response.displaytext,
+ iso.displaytext,
+ "Check ISO displaytext in response"
+ )