From a86160b389057e6f7241f49313c031db0b079a4f Mon Sep 17 00:00:00 2001 From: "Bitworks Software, Ltd" Date: Tue, 9 Jan 2018 15:25:34 +0700 Subject: [PATCH] Cloudstack 10170: Fix resource tags security bugs and add account tags support (#2350) This PR introduces several features and fixes some bugs: - account tags feature - fixed resource tags bugs which happened during tags search (found wrong entries because of mysql string to number translation - see #905, but this PR does more and fixes also resource access - vulnerability during list resource tags) - some marvin improvements (speed, sanity) Improved resource tags code: 1. Enhanced listTags security 2. Added support for account tags (account tags are required to support tags common for all users of an account) 3. Improved the tag management code (refactoring and cleanup) Marvin: 1. Fixed Marvin wait timeout between async pools. To decrease polling interval and improve CI speed. 2. Fixed /tmp/ to /tmp in zone configuration files. 3. Fixed + to os.path.join in log class. 4. Fixed + to os.path.join in deployDataCenter class. 5. Fixed typos in tag tests. 6. Modified Tags base class delete method. Deploy Datacenter script: 1. Improved deployDatacenter. Added option logdir to specify where script places results of evaluation. ConfigurationManagerImpl: 1. Added logging to ConfigurationManagerImpl to log when vlan is not found. Added test stubs for tags. Found accidental exception during simulator running after CI. tests_tags.py: 1. Fixed stale undeleted tags. 2. Changed region:India to scope:TestName. --- api/src/com/cloud/server/ResourceTag.java | 1 + .../ConfigurationManagerImpl.java | 3 + .../cloud/tags/TaggedResourceManagerImpl.java | 222 +++--- setup/dev/advanced.cfg | 2 +- setup/dev/advancedsg.cfg | 2 +- setup/dev/basic.cfg | 2 +- setup/dev/local.cfg | 2 +- setup/dev/s3.cfg | 2 +- test/integration/component/test_tags.py | 719 ++++++++++++++---- tools/marvin/marvin/cloudstackConnection.py | 4 +- tools/marvin/marvin/deployDataCenter.py | 53 +- tools/marvin/marvin/lib/base.py | 3 +- tools/marvin/marvin/marvinLog.py | 36 +- 13 files changed, 763 insertions(+), 288 deletions(-) diff --git a/api/src/com/cloud/server/ResourceTag.java b/api/src/com/cloud/server/ResourceTag.java index 067cb973e2f..0bd5d734e30 100644 --- a/api/src/com/cloud/server/ResourceTag.java +++ b/api/src/com/cloud/server/ResourceTag.java @@ -38,6 +38,7 @@ public interface ResourceTag extends ControlledEntity, Identity, InternalIdentit SecurityGroupRule(true, false), PublicIpAddress(true, true), Project(true, false), + Account(true, false), Vpc(true, true), NetworkACL(true, true), StaticRoute(true, false), diff --git a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java index 412ca5bead7..1632da95f95 100755 --- a/server/src/com/cloud/configuration/ConfigurationManagerImpl.java +++ b/server/src/com/cloud/configuration/ConfigurationManagerImpl.java @@ -3804,6 +3804,9 @@ public class ConfigurationManagerImpl extends ManagerBase implements Configurati @DB public boolean releasePublicIpRange(final long vlanDbId, final long userId, final Account caller) { VlanVO vlan = _vlanDao.findById(vlanDbId); + if(vlan == null) { + s_logger.warn("VLAN information for Account '" + caller + "', User '" + userId + "' VLAN '" + vlanDbId + "' is null. This is NPE situation."); + } // Verify range is dedicated boolean isAccountSpecific = false; diff --git a/server/src/com/cloud/tags/TaggedResourceManagerImpl.java b/server/src/com/cloud/tags/TaggedResourceManagerImpl.java index 9373a08fcf8..9803ce753a3 100644 --- a/server/src/com/cloud/tags/TaggedResourceManagerImpl.java +++ b/server/src/com/cloud/tags/TaggedResourceManagerImpl.java @@ -16,28 +16,13 @@ // under the License. package com.cloud.tags; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; -import javax.naming.ConfigurationException; - -import org.apache.cloudstack.api.Identity; -import org.apache.cloudstack.api.InternalIdentity; -import org.apache.cloudstack.context.CallContext; -import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; -import org.apache.commons.lang.StringUtils; -import org.apache.log4j.Logger; - -import com.cloud.api.query.dao.ResourceTagJoinDao; import com.cloud.dc.DataCenterVO; import com.cloud.domain.PartOf; import com.cloud.event.ActionEvent; import com.cloud.event.EventTypes; import com.cloud.exception.InvalidParameterValueException; import com.cloud.exception.PermissionDeniedException; +import com.cloud.offerings.NetworkOfferingVO; import com.cloud.network.LBHealthCheckPolicyVO; import com.cloud.network.as.AutoScaleVmGroupVO; import com.cloud.network.as.AutoScaleVmProfileVO; @@ -58,7 +43,6 @@ import com.cloud.network.vpc.NetworkACLVO; import com.cloud.network.vpc.StaticRouteVO; import com.cloud.network.vpc.VpcOfferingVO; import com.cloud.network.vpc.VpcVO; -import com.cloud.offerings.NetworkOfferingVO; import com.cloud.projects.ProjectVO; import com.cloud.server.ResourceTag; import com.cloud.server.ResourceTag.ResourceObjectType; @@ -72,6 +56,7 @@ import com.cloud.storage.VolumeVO; import com.cloud.tags.dao.ResourceTagDao; import com.cloud.user.Account; import com.cloud.user.AccountManager; +import com.cloud.user.AccountVO; import com.cloud.user.DomainManager; import com.cloud.user.OwnedBy; import com.cloud.user.UserVO; @@ -89,11 +74,28 @@ import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.vm.NicVO; import com.cloud.vm.UserVmVO; import com.cloud.vm.snapshot.VMSnapshotVO; +import org.apache.cloudstack.api.Identity; +import org.apache.cloudstack.api.InternalIdentity; +import org.apache.cloudstack.context.CallContext; +import org.apache.cloudstack.storage.datastore.db.StoragePoolVO; +import org.apache.commons.lang.StringUtils; +import org.apache.log4j.Logger; + +import org.apache.commons.collections.MapUtils; + +import javax.inject.Inject; +import javax.naming.ConfigurationException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; public class TaggedResourceManagerImpl extends ManagerBase implements TaggedResourceService { public static final Logger s_logger = Logger.getLogger(TaggedResourceManagerImpl.class); - private static final Map> s_typeMap = new HashMap>(); + private static final Map> s_typeMap = new HashMap<>(); static { s_typeMap.put(ResourceObjectType.UserVm, UserVmVO.class); s_typeMap.put(ResourceObjectType.Volume, VolumeVO.class); @@ -108,6 +110,7 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso s_typeMap.put(ResourceObjectType.SecurityGroupRule, SecurityGroupRuleVO.class); s_typeMap.put(ResourceObjectType.PublicIpAddress, IPAddressVO.class); s_typeMap.put(ResourceObjectType.Project, ProjectVO.class); + s_typeMap.put(ResourceObjectType.Account, AccountVO.class); s_typeMap.put(ResourceObjectType.Vpc, VpcVO.class); s_typeMap.put(ResourceObjectType.Nic, NicVO.class); s_typeMap.put(ResourceObjectType.NetworkACL, NetworkACLItemVO.class); @@ -140,8 +143,6 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso @Inject ResourceTagDao _resourceTagDao; @Inject - ResourceTagJoinDao _resourceTagJoinDao; - @Inject DomainManager _domainMgr; @Inject AccountDao _accountDao; @@ -194,6 +195,12 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso domainId = ((SecurityGroupVO)SecurityGroup).getDomainId(); } + if (resourceType == ResourceObjectType.Account) { + AccountVO account = (AccountVO)entity; + accountId = account.getId(); + domainId = account.getDomainId(); + } + // if the resource type is network acl, get the accountId and domainId from VPC following: NetworkACLItem -> NetworkACL -> VPC if (resourceType == ResourceObjectType.NetworkACL) { NetworkACLItemVO aclItem = (NetworkACLItemVO)entity; @@ -223,7 +230,23 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso if ((domainId == null) || ((accountId != null) && (domainId.longValue() == -1))) { domainId = _accountDao.getDomainIdForGivenAccountId(accountId); } - return new Pair(accountId, domainId); + return new Pair<>(accountId, domainId); + } + + private void checkResourceAccessible(Long accountId, Long domainId, String exceptionMessage) { + Account caller = CallContext.current().getCallingAccount(); + if (Objects.equals(domainId, -1)) + { + throw new CloudRuntimeException("Invalid DomainId: -1"); + } + if (accountId != null) { + _accountMgr.checkAccess(caller, null, false, _accountMgr.getAccount(accountId)); + } else if (domainId != null && !_accountMgr.isNormalUser(caller.getId())) { + //check permissions; + _accountMgr.checkAccess(caller, _domainMgr.getDomain(domainId)); + } else { + throw new PermissionDeniedException(exceptionMessage); + } } @Override @@ -237,59 +260,6 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso throw new InvalidParameterValueException("Invalid resource type " + resourceTypeStr); } - @Override - @DB - @ActionEvent(eventType = EventTypes.EVENT_TAGS_CREATE, eventDescription = "creating resource tags") - public List createTags(final List resourceIds, final ResourceObjectType resourceType, final Map tags, final String customer) { - final Account caller = CallContext.current().getCallingAccount(); - - final List resourceTags = new ArrayList(tags.size()); - - Transaction.execute(new TransactionCallbackNoReturn() { - @Override - public void doInTransactionWithoutResult(TransactionStatus status) { - for (String key : tags.keySet()) { - for (String resourceId : resourceIds) { - if (!resourceType.resourceTagsSupport()) { - throw new InvalidParameterValueException("The resource type " + resourceType + " doesn't support resource tags"); - } - - long id = getResourceId(resourceId, resourceType); - String resourceUuid = getUuid(resourceId, resourceType); - - Pair accountDomainPair = getAccountDomain(id, resourceType); - Long domainId = accountDomainPair.second(); - Long accountId = accountDomainPair.first(); - - if ((domainId != null) && (domainId == -1)) { - throw new CloudRuntimeException("Invalid DomainId : -1"); - } - if (accountId != null) { - _accountMgr.checkAccess(caller, null, false, _accountMgr.getAccount(accountId)); - } else if (domainId != null && !_accountMgr.isNormalUser(caller.getId())) { - //check permissions; - _accountMgr.checkAccess(caller, _domainMgr.getDomain(domainId)); - } else { - throw new PermissionDeniedException("Account " + caller + " doesn't have permissions to create tags" + " for resource " + key); - } - - String value = tags.get(key); - - if (value == null || value.isEmpty()) { - throw new InvalidParameterValueException("Value for the key " + key + " is either null or empty"); - } - - ResourceTagVO resourceTag = new ResourceTagVO(key, value, accountDomainPair.first(), accountDomainPair.second(), id, resourceType, customer, resourceUuid); - resourceTag = _resourceTagDao.persist(resourceTag); - resourceTags.add(resourceTag); - } - } - } - }); - - return resourceTags; - } - @Override public String getUuid(String resourceId, ResourceObjectType resourceType) { if (!StringUtils.isNumeric(resourceId)) { @@ -308,65 +278,119 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso @Override @DB - @ActionEvent(eventType = EventTypes.EVENT_TAGS_DELETE, eventDescription = "deleting resource tags") - public boolean deleteTags(List resourceIds, ResourceObjectType resourceType, Map tags) { - Account caller = CallContext.current().getCallingAccount(); + @ActionEvent(eventType = EventTypes.EVENT_TAGS_CREATE, eventDescription = "creating resource tags") + public List createTags(final List resourceIds, final ResourceObjectType resourceType, final Map tags, final String customer) { + final Account caller = CallContext.current().getCallingAccount(); + final List resourceTags = new ArrayList<>(tags.size()); + + Transaction.execute(new TransactionCallbackNoReturn() { + @Override + public void doInTransactionWithoutResult(TransactionStatus status) { + for (String key : tags.keySet()) { + for (String resourceId : resourceIds) { + if (!resourceType.resourceTagsSupport()) { + throw new InvalidParameterValueException("The resource type " + resourceType + " doesn't support resource tags"); + } + + long id = getResourceId(resourceId, resourceType); + String resourceUuid = getUuid(resourceId, resourceType); + + Pair accountDomainPair = getAccountDomain(id, resourceType); + Long domainId = accountDomainPair.second(); + Long accountId = accountDomainPair.first(); + + checkResourceAccessible(accountId, domainId, "Account '" + caller + + "' doesn't have permissions to create tags" + " for resource '" + id + "(" + key + ")'."); + + String value = tags.get(key); + + if (value == null || value.isEmpty()) { + throw new InvalidParameterValueException("Value for the key " + key + " is either null or empty"); + } + + ResourceTagVO resourceTag = new ResourceTagVO(key, value, accountDomainPair.first(), accountDomainPair.second(), id, resourceType, customer, resourceUuid); + resourceTag = _resourceTagDao.persist(resourceTag); + resourceTags.add(resourceTag); + } + } + } + }); + + return resourceTags; + } + + private List searchResourceTags(List resourceIds, ResourceObjectType resourceType) { + List resourceUuids = resourceIds.stream().map(resourceId -> getUuid(resourceId, resourceType)).collect(Collectors.toList()); SearchBuilder sb = _resourceTagDao.createSearchBuilder(); - sb.and().op("resourceId", sb.entity().getResourceId(), SearchCriteria.Op.IN); - sb.or("resourceUuid", sb.entity().getResourceUuid(), SearchCriteria.Op.IN); - sb.cp(); + sb.and("resourceUuid", sb.entity().getResourceUuid(), SearchCriteria.Op.IN); sb.and("resourceType", sb.entity().getResourceType(), SearchCriteria.Op.EQ); SearchCriteria sc = sb.create(); - sc.setParameters("resourceId", resourceIds.toArray()); - sc.setParameters("resourceUuid", resourceIds.toArray()); + sc.setParameters("resourceUuid", resourceUuids.toArray()); sc.setParameters("resourceType", resourceType); + return _resourceTagDao.search(sc, null); + } - List resourceTags = _resourceTagDao.search(sc, null); - ; - final List tagsToRemove = new ArrayList(); + @Override + @DB + @ActionEvent(eventType = EventTypes.EVENT_TAGS_DELETE, eventDescription = "deleting resource tags") + public boolean deleteTags(List resourceIds, ResourceObjectType resourceType, Map tags) { + Account caller = CallContext.current().getCallingAccount(); + if(s_logger.isDebugEnabled()) { + s_logger.debug("ResourceIds to Find " + String.join(", ", resourceIds)); + } + List resourceTags = searchResourceTags(resourceIds, resourceType); + final List tagsToDelete = new ArrayList<>(); // Finalize which tags should be removed for (ResourceTag resourceTag : resourceTags) { //1) validate the permissions + if(s_logger.isDebugEnabled()) { + s_logger.debug("Resource Tag Id: " + resourceTag.getResourceId()); + s_logger.debug("Resource Tag AccountId: " + resourceTag.getAccountId()); + } Account owner = _accountMgr.getAccount(resourceTag.getAccountId()); + if(s_logger.isDebugEnabled()) { + s_logger.debug("Resource Owner: " + owner); + } _accountMgr.checkAccess(caller, null, false, owner); //2) Only remove tag if it matches key value pairs - if (tags != null && !tags.isEmpty()) { + if (MapUtils.isEmpty(tags)) { + tagsToDelete.add(resourceTag); + } else { for (String key : tags.keySet()) { - boolean canBeRemoved = false; + boolean deleteTag = false; if (resourceTag.getKey().equalsIgnoreCase(key)) { String value = tags.get(key); if (value != null) { if (resourceTag.getValue().equalsIgnoreCase(value)) { - canBeRemoved = true; + deleteTag = true; } } else { - canBeRemoved = true; + deleteTag = true; } - if (canBeRemoved) { - tagsToRemove.add(resourceTag); + if (deleteTag) { + tagsToDelete.add(resourceTag); break; } } } - } else { - tagsToRemove.add(resourceTag); } } - if (tagsToRemove.isEmpty()) { - throw new InvalidParameterValueException("Unable to find tags by parameters specified"); + if (tagsToDelete.isEmpty()) { + throw new InvalidParameterValueException("Unable to find any tags which conform to specified delete parameters."); } //Remove the tags Transaction.execute(new TransactionCallbackNoReturn() { @Override public void doInTransactionWithoutResult(TransactionStatus status) { - for (ResourceTag tagToRemove : tagsToRemove) { + for (ResourceTag tagToRemove : tagsToDelete) { _resourceTagDao.remove(tagToRemove.getId()); - s_logger.debug("Removed the tag " + tagToRemove); + s_logger.debug("Removed the tag '" + tagToRemove + "' for resources (" + + String.join(", ", resourceIds) + ")"); } } }); @@ -375,7 +399,7 @@ public class TaggedResourceManagerImpl extends ManagerBase implements TaggedReso } @Override - public List listByResourceTypeAndId(ResourceObjectType type, long resourceId) { - return _resourceTagDao.listBy(resourceId, type); + public List listByResourceTypeAndId(ResourceObjectType resourceType, long resourceId) { + return _resourceTagDao.listBy(resourceId, resourceType); } } diff --git a/setup/dev/advanced.cfg b/setup/dev/advanced.cfg index d5762c39bce..d458b015e75 100644 --- a/setup/dev/advanced.cfg +++ b/setup/dev/advanced.cfg @@ -154,7 +154,7 @@ }, "logger": { - "LogFolderPath": "/tmp/" + "LogFolderPath": "/tmp" }, "globalConfig": [ { diff --git a/setup/dev/advancedsg.cfg b/setup/dev/advancedsg.cfg index 01f7fcc4662..5fbd02e6b14 100644 --- a/setup/dev/advancedsg.cfg +++ b/setup/dev/advancedsg.cfg @@ -109,7 +109,7 @@ }, "logger": { - "LogFolderPath": "/tmp/" + "LogFolderPath": "/tmp" }, "globalConfig": [ { diff --git a/setup/dev/basic.cfg b/setup/dev/basic.cfg index db8c59b09b4..b7f1486361a 100644 --- a/setup/dev/basic.cfg +++ b/setup/dev/basic.cfg @@ -110,7 +110,7 @@ }, "logger": { - "LogFolderPath": "/tmp/" + "LogFolderPath": "/tmp" }, "globalConfig": [ { diff --git a/setup/dev/local.cfg b/setup/dev/local.cfg index 89d56a56664..954d75fc331 100644 --- a/setup/dev/local.cfg +++ b/setup/dev/local.cfg @@ -25,7 +25,7 @@ }, "logger": { - "LogFolderPath": "/tmp/" + "LogFolderPath": "/tmp" }, "mgtSvr": [ { diff --git a/setup/dev/s3.cfg b/setup/dev/s3.cfg index a865e0dbc82..de28e5b2698 100644 --- a/setup/dev/s3.cfg +++ b/setup/dev/s3.cfg @@ -115,7 +115,7 @@ ], "logger": { - "LogFolderPath": "/tmp/" + "LogFolderPath": "/tmp" }, "mgtSvr": [ { diff --git a/test/integration/component/test_tags.py b/test/integration/component/test_tags.py index f9b0655016d..ed1aee7a0ee 100644 --- a/test/integration/component/test_tags.py +++ b/test/integration/component/test_tags.py @@ -185,9 +185,9 @@ class Services: "cidrlist": '0.0.0.0/0', }, # Cent OS 5.3 (64 bit) - "sleep": 60, + "sleep": 5, "ostype": 'CentOS 5.3 (64-bit)', - "timeout": 10, + "timeout": 5, "mode": 'advanced', } @@ -287,10 +287,12 @@ class TestResourceTags(cloudstackTestCase): raise Exception("Warning: Exception during cleanup : %s" % e) for tag in self.rm_tags: - tag['tag_obj'].delete(self.apiclient, tag['resid'], - tag['restype'], - {tag['key']: tag['value']}) - + for concrete_tag in tag['tags']: + Tag.delete(self.apiclient, + tag['resid'], + tag['restype'], + {concrete_tag['key']: concrete_tag['value']}) + return @attr(tags=["advanced"], required_hardware="false") @@ -404,7 +406,7 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=lb_rule.id, resourceType='LoadBalancer', @@ -541,7 +543,7 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=nat_rule.id, resourceType='portForwardingRule', @@ -683,7 +685,7 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=fw_rule.id, resourceType='FirewallRule', @@ -835,7 +837,7 @@ class TestResourceTags(cloudstackTestCase): ) self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=vpn.id, resourceType='VPN', @@ -876,12 +878,15 @@ class TestResourceTags(cloudstackTestCase): # 1. Create a tag on VM using createTags API # 2. Delete above created tag using deleteTags API + tag_key = 'scope' + tag_value = 'test_05_vm_tag' + self.debug("Creating a tag for user VM") tag = Tag.create( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -891,8 +896,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -901,15 +906,15 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual( tags[0].value, - 'India', + tag_value, "The tag value should match with the original value" ) vms = VirtualMachine.list( self.apiclient, listall=True, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( @@ -919,11 +924,11 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -935,8 +940,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( tags, @@ -1031,7 +1036,7 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.user_api_client, resourceIds=template.id, resourceType='Template', @@ -1124,7 +1129,7 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=iso.id, resourceType='ISO', @@ -1152,12 +1157,15 @@ class TestResourceTags(cloudstackTestCase): @attr(tags=["advanced", "basic"], required_hardware="false") def test_08_volume_tag(self): - """ Test creation, listing and deletion tagson volume + """ Test creation, listing and deletion tags on volume """ # Validate the following # 1. Create a tag on volume using createTags API # 2. Delete above created tag using deleteTags API + tag_key = 'scope' + tag_value = 'test_08_volume_tag' + if self.hypervisor.lower() == 'lxc': if not find_storage_pool_type(self.apiclient, storagetype='rbd'): self.skipTest("RBD storage type is required for data volumes for LXC") @@ -1181,7 +1189,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, resourceIds=volume.id, resourceType='volume', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -1191,8 +1199,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='volume', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -1201,14 +1209,14 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual( tags[0].value, - 'India', + tag_value, 'The tag should have original value' ) vols = Volume.list(self.apiclient, listall=True, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(vols, list), @@ -1218,11 +1226,11 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=volume.id, resourceType='volume', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -1234,7 +1242,7 @@ class TestResourceTags(cloudstackTestCase): resourceType='volume', account=self.account.name, domainid=self.account.domainid, - key='region' + key=tag_key ) self.assertEqual( tags, @@ -1245,7 +1253,7 @@ class TestResourceTags(cloudstackTestCase): @attr(tags=["advanced", "basic"], required_hardware="false") def test_09_snapshot_tag(self): - """ Test creation, listing and deletion tag son snapshot + """ Test creation, listing and deletion tags on snapshot """ # Validate the following # 1. Create a tag on snapshot using createTags API @@ -1321,7 +1329,7 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=snapshot.id, resourceType='snapshot', @@ -1350,12 +1358,15 @@ class TestResourceTags(cloudstackTestCase): @attr(tags=["advanced"], required_hardware="false") def test_10_network_tag(self): - """ Testcreation, listing and deletion tags on guest network + """ Test creation, listing and deletion tags on guest network """ # Validate the following # 1. Create a tag on Network using createTags API # 2. Delete above created tag using deleteTags API + tag_key = 'scope' + tag_value = 'test_10_network_tag' + self.debug("Fetching the network details for account: %s" % self.account.name) networks = Network.list( @@ -1378,7 +1389,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, resourceIds=network.id, resourceType='Network', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -1388,8 +1399,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='Network', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -1398,7 +1409,7 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual( tags[0].value, - 'India', + tag_value, 'The tag should have original value' ) @@ -1407,8 +1418,8 @@ class TestResourceTags(cloudstackTestCase): account=self.account.name, domainid=self.account.domainid, listall=True, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(networks, list), @@ -1418,11 +1429,11 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=network.id, resourceType='Network', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -1434,8 +1445,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='Network', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( tags, @@ -1452,6 +1463,9 @@ class TestResourceTags(cloudstackTestCase): # 1. Create a tag on VM using createTags API # 2. Delete above created tag using deleteTags API + tag_key = 'scope' + tag_value = 'test_11_migrate_tagged_vm_del' + if self.hypervisor.lower() in ['lxc']: self.skipTest("vm migrate feature is not supported on %s" % self.hypervisor.lower()) @@ -1496,7 +1510,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -1506,8 +1520,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -1517,7 +1531,7 @@ class TestResourceTags(cloudstackTestCase): self.assertEqual( tags[0].value, - 'India', + tag_value, 'The tag should have original value' ) @@ -1527,11 +1541,11 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -1543,8 +1557,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( tags, @@ -1562,12 +1576,15 @@ class TestResourceTags(cloudstackTestCase): # 2. Add same tag in upper case. # 3. Verify that tag creation failed. + tag_key = 'scope' + tag_value = 'test_13_tag_case_insensitive' + self.debug("Creating a tag for user VM") tag_1 = Tag.create( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag_1.__dict__) @@ -1577,8 +1594,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -1588,25 +1605,25 @@ class TestResourceTags(cloudstackTestCase): self.assertEqual( tags[0].value, - 'India', + tag_value, 'The tag should have original value' ) try: Tag.create(self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'REGION': 'INDIA'}) + tags={tag_key.upper(): tag_value.uppper()}) except Exception as e: pass else: assert("Creating same tag in upper case succeeded") try: - tag_1.delete( + Tag.delete( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -1618,8 +1635,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( tags, @@ -1637,13 +1654,16 @@ class TestResourceTags(cloudstackTestCase): # 1. Create more than 10 tags to VM using createTags API # 2. Create a tag with special characters on VM using createTags API + tag_key = 'scope' + tag_value = 'test_14_special_char_mutiple_tags' + self.debug("Creating a tag for user VM") tag = Tag.create( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', tags={ - 'region': 'India', + tag_key: tag_value, 'offering': 'high', 'type': 'webserver', 'priority': 'critical', @@ -1664,8 +1684,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -1674,16 +1694,16 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual( tags[0].value, - 'India', + tag_value, 'The tag should have original value' ) # Cleanup - tag.delete( + Tag.delete( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', tags={ - 'region': 'India', + tag_key: tag_value, 'offering': 'high', 'type': 'webserver', 'priority': 'critical', @@ -1707,6 +1727,9 @@ class TestResourceTags(cloudstackTestCase): # 2. Create a tag on projects using createTags API # 3. Delete the tag. + tag_key = 'scope' + tag_value = 'test_15_project_tag' + # Create project as a domain admin project = Project.create( self.apiclient, @@ -1724,7 +1747,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, resourceIds=project.id, resourceType='project', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -1733,7 +1756,7 @@ class TestResourceTags(cloudstackTestCase): listall=True, resourceType='project', resourceIds=project.id, - key='region', + key=tag_key, ) self.debug("tags = %s" % tags) @@ -1744,15 +1767,15 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual( tags[0].value, - 'India', + tag_value, 'The tag should have original value' ) projects = Project.list( self.apiclient, listall=True, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( @@ -1763,11 +1786,11 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=project.id, resourceType='project', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -1779,8 +1802,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='project', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( tags, @@ -1799,8 +1822,11 @@ class TestResourceTags(cloudstackTestCase): # 3. Login with other account and query the tags using # listTags API + tag_key = 'scope' + tag_value = 'test_16_query_tags_other_account' + self.debug("Creating user accounts..") - + user_account = Account.create( self.apiclient, self.services["user"], @@ -1836,7 +1862,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, resourceIds=iso.id, resourceType='ISO', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -1846,7 +1872,7 @@ class TestResourceTags(cloudstackTestCase): resourceType='ISO', account=user_account.name, domainid=user_account.domainid, - key='region', + key=tag_key, ) self.assertEqual( isinstance(tags, list), @@ -1855,7 +1881,7 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual( tags[0].value, - 'India', + tag_value, "The tag value should match with the original value" ) @@ -1866,7 +1892,7 @@ class TestResourceTags(cloudstackTestCase): resourceType='ISO', account=other_user_account.name, domainid=other_user_account.domainid, - key='region', + key=tag_key, ) self.assertEqual( @@ -1875,6 +1901,16 @@ class TestResourceTags(cloudstackTestCase): "List tags should return empty response" ) + try: + Tag.delete( + self.apiclient, + resourceIds=iso.id, + resourceType='ISO', + tags={tag_key: tag_value} + ) + except Exception as e: + self.fail("Failed to delete the tag - %s" % e) + return @attr(tags=["advanced", "basic"], required_hardware="false") @@ -1887,6 +1923,9 @@ class TestResourceTags(cloudstackTestCase): # 3. Login with admin account and query the tags using # listTags API + tag_key = 'scope' + tag_value = 'test_17_query_tags_admin_account' + self.debug("Creating user accounts..") user_account = Account.create( @@ -1913,7 +1952,7 @@ class TestResourceTags(cloudstackTestCase): Tag.create(self.apiclient, resourceIds=iso.id, resourceType='ISO', - tags={'region': 'India'}) + tags={tag_key: tag_value}) tags = Tag.list( self.apiclient, @@ -1921,7 +1960,7 @@ class TestResourceTags(cloudstackTestCase): resourceType='ISO', account=user_account.name, domainid=user_account.domainid, - key='region', + key=tag_key, ) self.assertEqual( isinstance(tags, list), @@ -1930,7 +1969,7 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual( tags[0].value, - 'India', + tag_value, "The tag value should match with the original value" ) @@ -1939,7 +1978,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, listall=True, resourceType='ISO', - key='region', + key=tag_key, ) self.assertEqual( isinstance(tags, list), @@ -1948,10 +1987,20 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual( tags[0].value, - 'India', + tag_value, 'The tag should have original value' ) + try: + Tag.delete( + self.apiclient, + resourceIds=iso.id, + resourceType='ISO', + tags={tag_key: tag_value} + ) + except Exception as e: + self.fail("Failed to delete the tag - %s" % e) + return @attr(tags=["advanced", "basic", "simulator"], required_hardware="false") @@ -1962,12 +2011,15 @@ class TestResourceTags(cloudstackTestCase): # 1. Create a tag on supported resource type(ex:vms) # 2. Run the list API commands with passing invalid key parameter + tag_key = 'scope' + tag_value = 'test_18_invalid_list_parameters' + self.debug("Creating a tag for user VM") tag = Tag.create( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -1979,14 +2031,15 @@ class TestResourceTags(cloudstackTestCase): self.rm_tags.append({'tag_obj': tag, 'restype': 'userVM', 'resid': self.vm_1.id, - 'key': 'region', - 'value': 'India'}) + 'tags': [ + {'key': tag_key, 'value': tag_value} + ]}) self.debug("Passing invalid key parameter to the listAPI for vms") vms = VirtualMachine.list(self.apiclient, - **{'tags[0].key': 'region111', - 'tags[0].value': 'India', + **{'tags[0].key': tag_key + '1', + 'tags[0].value': tag_value, 'listall': 'True'} ) self.assertEqual( @@ -2006,12 +2059,15 @@ class TestResourceTags(cloudstackTestCase): # 1. Deletion of a tag without any errors. # 2. Add same tag. + tag_key = 'scope' + tag_value = 'test_19_delete_add_same_tag' + self.debug("Creating a tag for user VM") tag = Tag.create( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -2021,8 +2077,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -2032,17 +2088,17 @@ class TestResourceTags(cloudstackTestCase): self.assertEqual( tags[0].value, - "India", + tag_value, "Tag created with incorrect value" ) self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -2054,8 +2110,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( tags, @@ -2067,7 +2123,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -2077,8 +2133,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -2087,17 +2143,17 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual(tags[0].value, - "India", + tag_value, "Tag created with incorrect value" ) self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -2107,8 +2163,11 @@ class TestResourceTags(cloudstackTestCase): def test_20_create_tags_multiple_resources(self): "Test creation of same tag on multiple resources" - self.debug("Creating volume for account: %s " % - self.account.name) + tag_key = 'scope' + tag_value = 'test_20_create_tags_multiple_resources' + + self.debug("Creating volume for account: %s " % self.account.name) + volume = Volume.create( self.apiclient, self.services["volume"], @@ -2126,7 +2185,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, resourceIds=volume.id, resourceType='volume', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -2136,7 +2195,7 @@ class TestResourceTags(cloudstackTestCase): resourceType='volume', account=self.account.name, domainid=self.account.domainid, - key='region', + key=tag_key, ) self.assertEqual( isinstance(tags, list), @@ -2145,7 +2204,7 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual( tags[0].value, - 'India', + tag_value, 'The tag should have original value' ) @@ -2154,7 +2213,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -2164,8 +2223,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -2175,17 +2234,17 @@ class TestResourceTags(cloudstackTestCase): self.assertEqual( tags[0].value, - "India", - "Tag created with incorrect value" + tag_value, + "Expected tag value is incorrect" ) self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -2197,8 +2256,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( tags, @@ -2212,6 +2271,9 @@ class TestResourceTags(cloudstackTestCase): def test_21_create_tag_stopped_vm(self): "Test creation of tag on stopped vm." + tag_key = 'scope' + tag_value = 'test_21_create_tag_stopped_vm' + try: self.debug("Stopping the virtual machine: %s" % self.vm_1.name) # Stop virtual machine @@ -2222,7 +2284,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -2232,8 +2294,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -2243,16 +2305,16 @@ class TestResourceTags(cloudstackTestCase): self.assertEqual( tags[0].value, - "India", + tag_value, "Tag created with incorrect value" ) self.debug("Deleting the created tag..") - tag.delete( + Tag.delete( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Exception occured - %s" % e) @@ -2262,6 +2324,9 @@ class TestResourceTags(cloudstackTestCase): def test_22_create_tag_destroyed_vm(self): "Test creation of tag on stopped vm." + tag_key = 'scope' + tag_value = 'test_22_create_tag_destroyed_vm' + self.debug("Destroying instance: %s" % self.vm_1.name) self.vm_1.delete(self.apiclient, expunge=False) @@ -2270,7 +2335,7 @@ class TestResourceTags(cloudstackTestCase): self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -2280,8 +2345,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='userVM', account=self.account.name, domainid=self.account.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -2291,17 +2356,17 @@ class TestResourceTags(cloudstackTestCase): self.assertEqual( tags[0].value, - "India", + tag_value, "Tag created with incorrect value" ) self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.apiclient, resourceIds=self.vm_1.id, resourceType='userVM', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -2405,8 +2470,8 @@ class TestResourceTags(cloudstackTestCase): return @attr(tags=["advanced"], required_hardware="false") - def test_24_public_IP_tag(self): - """ Testcreation, adding and removing tag on public IP address + def test_24_public_ip_tag(self): + """ Test creation, adding and removing tag on public IP address """ # Validate the following # 1. Create a domain and admin account under the new domain @@ -2414,6 +2479,9 @@ class TestResourceTags(cloudstackTestCase): # 3. Delete above created tag using deleteTags API # 4. Perform steps 2&3 using domain-admin + tag_key = 'scope' + tag_value = 'test_24_public_ip_tag' + self.debug("Creating a sub-domain under: %s" % self.domain.name) self.child_domain = Domain.create( self.apiclient, @@ -2479,7 +2547,7 @@ class TestResourceTags(cloudstackTestCase): self.dom_admin_api_client, resourceIds=public_ip.ipaddress.id, resourceType='PublicIpAddress', - tags={'region': 'India'} + tags={tag_key: tag_value} ) self.debug("Tag created: %s" % tag.__dict__) @@ -2489,8 +2557,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='PublicIpAddress', account=self.child_do_admin.name, domainid=self.child_do_admin.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(tags, list), @@ -2499,7 +2567,7 @@ class TestResourceTags(cloudstackTestCase): ) self.assertEqual( tags[0].value, - 'India', + tag_value, 'The tag should have original value' ) publicIps = PublicIPAddress.list( @@ -2507,8 +2575,8 @@ class TestResourceTags(cloudstackTestCase): account=self.child_do_admin.name, domainid=self.child_do_admin.domainid, listall=True, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( isinstance(publicIps, list), @@ -2518,11 +2586,11 @@ class TestResourceTags(cloudstackTestCase): self.debug("Deleting the created tag..") try: - tag.delete( + Tag.delete( self.dom_admin_api_client, resourceIds=public_ip.ipaddress.id, resourceType='PublicIpAddress', - tags={'region': 'India'} + tags={tag_key: tag_value} ) except Exception as e: self.fail("Failed to delete the tag - %s" % e) @@ -2534,8 +2602,8 @@ class TestResourceTags(cloudstackTestCase): resourceType='PublicIpAddress', account=self.child_do_admin.name, domainid=self.child_do_admin.domainid, - key='region', - value='India' + key=tag_key, + value=tag_value ) self.assertEqual( tags, @@ -2543,3 +2611,366 @@ class TestResourceTags(cloudstackTestCase): "List tags should return empty response" ) return + + def __test_account_tags(self, apiclient, account, listall = False): + set_tags = {'primary-contact-name': 'John Doe', + 'primary-contact-phone': '1-022-333-444'} + + Tag.create( + apiclient, + resourceIds=account.id, + resourceType='Account', + tags=set_tags) + + received_tags = Tag.list( + apiclient, + resourceId=account.id, + listAll=listall, + resourceType='Account') + + self.assertEqual( + isinstance(received_tags, list), + True, + "List tags should return list response." + ) + + received_tag_map = {} + for t in received_tags: + received_tag_map[t.key] = t.value + + self.assertEqual( + set_tags, + received_tag_map, + "Tags saved and received differ." + ) + + try: + Tag.delete( + apiclient, + resourceIds=account.id, + resourceType='Account', + tags=set_tags) + except Exception as e: + self.fail("Failed to delete the tag - %s" % e) + + received_tags_removed = Tag.list( + apiclient, + resourceId=account.id, + listAll=listall, + resourceType='Account') + + self.assertEqual( + received_tags_removed, + None, + "List tags should return empty list response when tags are removed." + ) + return + + @attr(tags=["advanced","basic"], required_hardware="false") + def test_25_admin_account_tags(self): + '''Test create, list, delete tag for admin account from admin account''' + self.debug("Creating a tag for Admin account") + admin_account = Account.list(self.apiclient, name='admin') + self.__test_account_tags(self.apiclient, admin_account[0]) + return + + @attr(tags=["advanced","basic"], required_hardware="false") + def test_26_domain_admin_account_tags(self): + child_domain = Domain.create( + self.apiclient, + services=self.services["domain"], + parentdomainid=self.domain.id + ) + child_domain_admin = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=child_domain.id + ) + # Cleanup the resources created at end of test + self.cleanup.append(child_domain_admin) + self.cleanup.append(child_domain) + domain_admin_api_client = self.testClient.getUserApiClient( + UserName=child_domain_admin.name, + DomainName=child_domain_admin.domain + ) + self.__test_account_tags(domain_admin_api_client, child_domain_admin) + return + + @attr(tags=["advanced","basic"], required_hardware="false") + def test_27_regular_user_account_tags(self): + regular_account = Account.create( + self.apiclient, + self.services["account"], + admin=False, + domainid=self.domain.id + ) + # Cleanup the resources created at end of test + self.cleanup.append(regular_account) + regular_account_api_client = self.testClient.getUserApiClient(UserName=regular_account.name, DomainName=self.domain.name) + self.__test_account_tags(regular_account_api_client, regular_account) + return + + @attr(tags=["advanced","basic"], required_hardware="false") + def test_28_admin_access_domain_admin_account_tags(self): + '''Test create, list, delete tag for domain admin account from admin account''' + child_domain = Domain.create( + self.apiclient, + services=self.services["domain"], + parentdomainid=self.domain.id + ) + child_domain_admin = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=child_domain.id + ) + # Cleanup the resources created at end of test + self.cleanup.append(child_domain_admin) + self.cleanup.append(child_domain) + self.__test_account_tags(self.apiclient, child_domain_admin, listall = True) + return + + @attr(tags=["advanced","basic"], required_hardware="false") + def test_29_admin_access_user_account_tags(self): + '''Test create, list, delete tag for user account from admin account''' + regular_account = Account.create( + self.apiclient, + self.services["account"], + admin=False + ) + # Cleanup the resources created at end of test + self.cleanup.append(regular_account) + self.__test_account_tags(self.apiclient, regular_account, listall = True) + return + + @attr(tags=["advanced","basic"], required_hardware="false") + def test_30_domain_admin_access_user_account_same_domain_tags(self): + '''Test create, list, delete tag for user account from admin account''' + + child_domain = Domain.create( + self.apiclient, + services=self.services["domain"], + parentdomainid=self.domain.id + ) + child_domain_admin = Account.create( + self.apiclient, + self.services["account"], + admin=True, + domainid=child_domain.id + ) + + regular_account = Account.create( + self.apiclient, + self.services["account"], + admin=False, + domainid=child_domain.id + ) + # Cleanup the resources created at end of test + self.cleanup.append(regular_account) + + # Cleanup the resources created at end of test + self.cleanup.append(child_domain_admin) + self.cleanup.append(child_domain) + + domain_admin_api_client = self.testClient.getUserApiClient( + UserName=child_domain_admin.name, + DomainName=child_domain.name + ) + self.__test_account_tags(domain_admin_api_client, regular_account, listall = True) + return + + @attr(tags=["advanced","basic"], required_hardware="false") + def test_31_user_cant_remove_update_admin_tags(self): + '''Tests that an user is unable to remove, modify tags created by admin but should access''' + + tag_key_user = 'scope_user' + tag_value_user = 'test_31_user_cant_remove_update_admin_tags' + + tag_key_admin = 'scope_admin' + tag_value_admin = 'test_31_user_cant_remove_update_admin_tags' + + regular_account = Account.create( + self.apiclient, + self.services["account"], + admin=False + ) + self.cleanup.append(regular_account) + + regular_account_api_client = self.testClient.getUserApiClient(UserName=regular_account.name, DomainName=self.domain.name) + + def create_admin_tag(): + return Tag.create( + self.apiclient, + resourceIds=regular_account.id, + resourceType='Account', + tags={ tag_key_admin: tag_value_admin}) + + + def create_user_tag(): + return Tag.create( + regular_account_api_client, + resourceIds=regular_account.id, + resourceType='Account', + tags={ tag_key_user: tag_value_user}) + + create_admin_tag() + create_user_tag() + + # + # List test expressions + # + def list_tags(apiclient, listAll): + return Tag.list( + apiclient, + resourceId=regular_account.id, + listAll=listAll, + resourceType='Account') + + def tags_to_map(tags): + m = {} + for t in tags: + m[t.key] = t.value + return m + + # admin requests user account tags and gets None (without listall) + received_tags_admin = list_tags(self.apiclient, False) + self.assertEqual( + received_tags_admin, + None, + "List tags should return empty list response when tags are not set on self-owned account." + ) + + # admin requests user account tags and gets all (with listall) + received_tags_admin_listall = list_tags(self.apiclient, True) + self.assertEqual( + tags_to_map(received_tags_admin_listall), + {tag_key_admin: tag_value_admin, tag_key_user: tag_value_user}, + "List (with listAll=true) tags should return information for admin tags and user tags" + ) + + # user requests own account tags and receives all (without listall) + received_tags_user = list_tags(regular_account_api_client, False) + self.assertEqual( + tags_to_map(received_tags_user), + {tag_key_admin: tag_value_admin, tag_key_user: tag_value_user}, + "List (with listAll=false) tags should return information for user tags" + ) + + # user requests own account tags and receives all (with listall) + received_tags_user_listall = list_tags(regular_account_api_client, True) + self.assertEqual( + tags_to_map(received_tags_user_listall), + {tag_key_admin: tag_value_admin, tag_key_user: tag_value_user}, + "List (with listAll=false) tags should return information for user tags" + ) + + # + # Delete test expressions + # + + def delete_tags(apiclient, tags): + Tag.delete( + apiclient, + resourceIds=regular_account.id, + resourceType='Account', + tags=tags) + + # user tries to delete admin tag on own account and succeeds + try: + delete_tags(regular_account_api_client, {tag_key_admin: tag_value_admin}) + except Exception as e: + self.fail("Regular user is not able to delete administrator tag on own account - %s" % e) + + # user tries to delete user tag and succeeds + try: + delete_tags(regular_account_api_client, {tag_key_user: tag_value_user}) + except Exception as e: + self.fail("Regular user is not able to delete own tag - %s" % e) + + # recover tag to run admin tests + create_user_tag() + create_admin_tag() + + # admin tries to delete tags and succeeds + try: + delete_tags(self.apiclient, {tag_key_admin: tag_value_admin, tag_key_user: tag_value_user}) + except Exception as e: + self.fail("Administrator is not able to delete a tag - %s" % e) + + return + + @attr(tags=["advanced","basic"], required_hardware="false") + def test_32_user_a_doesnt_have_access_to_user_b_tags(self): + '''Test resource security between regular accounts A and B''' + + tag_key_user1 = 'scope_user1' + tag_value_user1 = 'test_32_user_a_doesnt_have_access_to_user_b_tags-user1' + + tag_key_user2 = 'scope_user2' + tag_value_user2 = 'test_32_user_a_doesnt_have_access_to_user_b_tags-user2' + + regular_account1 = Account.create( + self.apiclient, + self.services["account"], + admin=False + ) + self.cleanup.append(regular_account1) + + regular_account_api_client1 = self.testClient.getUserApiClient(UserName=regular_account1.name, DomainName=self.domain.name) + + regular_account2 = Account.create( + self.apiclient, + self.services["account"], + admin=False + ) + self.cleanup.append(regular_account2) + + regular_account_api_client2 = self.testClient.getUserApiClient(UserName=regular_account2.name, DomainName=self.domain.name) + + Tag.create( + regular_account_api_client1, + resourceIds=regular_account1.id, + resourceType='Account', + tags={tag_key_user1: tag_value_user1}) + + Tag.create( + regular_account_api_client2, + resourceIds=regular_account2.id, + resourceType='Account', + tags={tag_key_user2: tag_value_user2}) + + try: + Tag.list( + regular_account_api_client1, + resourceId=regular_account2.id, + listAll=listAll, + resourceType='Account') + except Exception as e: + pass + else: + self.fail("User1 has access to list tags of User2.") + + try: + Tag.delete( + regular_account_api_client1, + resourceIds=regular_account2.id, + resourceType='Account', + tags={tag_key_user2: tag_value_user2}) + except Exception as e: + pass + else: + self.fail("User1 has access to delete tags of User2.") + + try: + Tag.create( + regular_account_api_client1, + resourceIds=regular_account2.id, + resourceType='Account', + tags={tag_key_user1: tag_value_user1}) + except Exception as e: + pass + else: + self.fail("User1 has access to create tags for User2.") + + return diff --git a/tools/marvin/marvin/cloudstackConnection.py b/tools/marvin/marvin/cloudstackConnection.py index d044fdd0eb4..826069742fe 100644 --- a/tools/marvin/marvin/cloudstackConnection.py +++ b/tools/marvin/marvin/cloudstackConnection.py @@ -103,8 +103,8 @@ class CSConnection(object): elif job_status == JOB_FAILED: raise Exception("Job failed: %s"\ % async_response) - time.sleep(5) - timeout -= 5 + time.sleep(1) + timeout -= 1 self.logger.debug("=== JobId:%s is Still Processing, " "Will TimeOut in:%s ====" % (str(jobid), str(timeout))) diff --git a/tools/marvin/marvin/deployDataCenter.py b/tools/marvin/marvin/deployDataCenter.py index 8d553c872e1..0ef3f3c3ea9 100644 --- a/tools/marvin/marvin/deployDataCenter.py +++ b/tools/marvin/marvin/deployDataCenter.py @@ -35,6 +35,7 @@ from marvin.lib.utils import (random_gen) from marvin.config.test_data import test_data from sys import exit import os +import errno import pickle from time import sleep, strftime, localtime from optparse import OptionParser @@ -65,18 +66,16 @@ class DeployDataCenters(object): def __persistDcConfig(self): try: if self.__logFolderPath: - dc_file_path = self.__logFolderPath + "/dc_entries.obj" + dc_file_path = os.path.join(self.__logFolderPath, "dc_entries.obj") else: ts = strftime("%b_%d_%Y_%H_%M_%S", localtime()) dc_file_path = "dc_entries_" + str(ts) + ".obj" + file_to_write = open(dc_file_path, 'w') if file_to_write: pickle.dump(self.__cleanUp, file_to_write) - print "\n=== Data Center Settings are dumped to %s===" % \ - dc_file_path - self.__tcRunLogger.debug( - "\n=== Data Center Settings are dumped to %s===" % - dc_file_path) + print "\n=== Data Center Settings are dumped to %s===" % dc_file_path + self.__tcRunLogger.debug("\n=== Data Center Settings are dumped to %s===" % dc_file_path) except Exception as e: print "Exception Occurred while persisting DC Settings: %s" % \ GetDetailExceptionInfo(e) @@ -1110,38 +1109,46 @@ class DeleteDataCenters: finally: return ret +def mkdirpath(path): + try: + os.makedirs(path) + except OSError as exc: + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: + raise if __name__ == "__main__": ''' @Desc : This module facilitates the following: 1. Deploying DataCenter by using the input provided configuration. - EX: python deployDataCenter.py -i + EX: python deployDataCenter.py -i [-l ] 2. Removes a created DataCenter by providing the input configuration file and data center settings file EX: python deployDataCenter.py -i - -r + -r [-l ] ''' parser = OptionParser() - parser.add_option("-i", "--input", action="store", - default=None, dest="input", - help="the path \ - where the json config file generated") + parser.add_option("-i", "--input", action="store", default=None, dest="input", + help="The path where the json zones config file is located.") + + parser.add_option("-r", "--remove", action="store", default=None, dest="remove", + help="The path to file where the created dc entries are kept.") + + parser.add_option("-l", "--logdir", action="store", default=None, dest="logdir", + help="The directory where result logs of running the script are stored:" + "[dc_entries.obj, failed_plus_exceptions.txt, runinfo.txt]." + + "Created automatically if doesn't exists.") - parser.add_option("-r", "--remove", action="store", - default=None, dest="remove", - help="path to file\ - where the created dc entries are kept") (options, args) = parser.parse_args() ''' Verify the input validity ''' if options.input is None and options.remove is None: - print "\n==== For DeployDataCenter: Please Specify a " \ - "Valid Input Configuration File====" - print "\n==== For DeleteDataCenters: Please Specify a " \ - "Valid Input Configuration File and DC Settings====" + print "\n==== For DeployDataCenter: Please Specify a valid Input Configuration File====" + print "\n==== For DeleteDataCenters: Please Specify a valid Input Configuration File and DC Settings====" exit(1) ''' @@ -1161,8 +1168,10 @@ if __name__ == "__main__": cfg = configGenerator.getSetupConfig(options.input) log = cfg.logger - ret = log_obj.createLogs("DeployDataCenter", - log) + if options.logdir != None: + mkdirpath(options.logdir) + + ret = log_obj.createLogs("DeployDataCenter", log, options.logdir, options.logdir == None) if ret != FAILED: log_folder_path = log_obj.getLogFolderPath() tc_run_logger = log_obj.getLogger() diff --git a/tools/marvin/marvin/lib/base.py b/tools/marvin/marvin/lib/base.py index 02ee138aa1f..b7f25ace9bc 100755 --- a/tools/marvin/marvin/lib/base.py +++ b/tools/marvin/marvin/lib/base.py @@ -4304,7 +4304,8 @@ class Tag: }) return Tag(apiclient.createTags(cmd).__dict__) - def delete(self, apiclient, resourceIds, resourceType, tags): + @classmethod + def delete(cls, apiclient, resourceIds, resourceType, tags): """Delete tags""" cmd = deleteTags.deleteTagsCmd() diff --git a/tools/marvin/marvin/marvinLog.py b/tools/marvin/marvin/marvinLog.py index ea8eaee7dff..582d2e14457 100644 --- a/tools/marvin/marvin/marvinLog.py +++ b/tools/marvin/marvin/marvinLog.py @@ -128,7 +128,7 @@ class MarvinLog: def createLogs(self, test_module_name=None, log_cfg=None, - user_provided_logpath=None): + user_provided_logpath=None, use_temp_path=True): ''' @Name : createLogs @Desc : Gets the Logger with file paths initialized and created @@ -140,29 +140,34 @@ class MarvinLog: If user provided log path is available, then one in cfg will not be picked up. + use_temp_path: Boolean value which specifies either logs will + be prepended by random path or not. @Output : SUCCESS\FAILED ''' try: - temp_ts = time.strftime("%b_%d_%Y_%H_%M_%S", - time.localtime()) + temp_ts = time.strftime("%b_%d_%Y_%H_%M_%S", time.localtime()) + if test_module_name is None: temp_path = temp_ts + "_" + random_gen() else: - temp_path = str(test_module_name) + \ - "__" + str(temp_ts) + "_" + random_gen() + temp_path = str(test_module_name) + "__" + str(temp_ts) + "_" + random_gen() if user_provided_logpath: - temp_dir = user_provided_logpath + "/MarvinLogs" + temp_dir = os.path.join(user_provided_logpath, "MarvinLogs") elif ((log_cfg is not None) and ('LogFolderPath' in log_cfg.__dict__.keys()) and (log_cfg.__dict__.get('LogFolderPath') is not None)): - temp_dir = \ - log_cfg.__dict__.get('LogFolderPath') + "/MarvinLogs" + temp_dir = os.path.join(log_cfg.__dict__.get('LogFolderPath'), "MarvinLogs") - self.__logFolderDir = temp_dir + "//" + temp_path - print "\n==== Log Folder Path: %s. " \ - "All logs will be available here ====" \ - % str(self.__logFolderDir) + if use_temp_path == True: + self.__logFolderDir = os.path.join(temp_dir, temp_path) + else: + if test_module_name == None: + self.__logFolderDir = temp_dir + else: + self.__logFolderDir = os.path.join(temp_dir, str(test_module_name)) + + print "\n==== Log Folder Path: %s. All logs will be available here ====" % str(self.__logFolderDir) os.makedirs(self.__logFolderDir) ''' @@ -171,9 +176,10 @@ class MarvinLog: 2. RunLog contains the complete Run Information for Test Run 3. ResultFile contains the TC result information for Test Run ''' - tc_failed_exception_log = \ - self.__logFolderDir + "/failed_plus_exceptions.txt" - tc_run_log = self.__logFolderDir + "/runinfo.txt" + + tc_failed_exception_log = os.path.join(self.__logFolderDir, "failed_plus_exceptions.txt") + tc_run_log = os.path.join(self.__logFolderDir, "runinfo.txt") + if self.__setLogHandler(tc_run_log, log_level=logging.DEBUG) != FAILED: self.__setLogHandler(tc_failed_exception_log,