This is an automated email from the ASF dual-hosted git repository. rohit pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/cloudstack.git
The following commit(s) were added to refs/heads/master by this push: new 3e2ef19 Cloudstack 10064: Secondary storage Usage for uploadedVolume is not collected (#2258) 3e2ef19 is described below commit 3e2ef197dbd05db3dea8b436082e3dacb3459cb0 Author: PranaliM <pranali_ma...@accelerite.com> AuthorDate: Wed Dec 27 13:21:54 2017 +0530 Cloudstack 10064: Secondary storage Usage for uploadedVolume is not collected (#2258) Description: For Volumes on Secondary Storage, (Uploaded Volume) the usage is not accounted for. The fix is implemented as follows: A new Usage Type is added for the Volume on secondary storage : VOLUME_SECONDARY (id=26) A new storage type, 'Volume' is defined. When a volume is uploaded and the usage server executes next,entry will be added to the usage_storage helper table for all the volumes uploaded since the Usage server executed last. When the uploaded volume is attached, the 'deleted' column in the usage_storage table is set to the time-stamp when the volume was deleted 2 entries will be added to the cloud_usage table with usage_type=26 and usage_type=6 (Volume usage on primary). One for the duration the volume was on primary and other for the duration it was on secondary. Entry is added to the helper table volume_usage for accounting for the primary storage.Next execution of the usage server and on-wards, usage entry for usage_type=6 only will be added. --- .../org/apache/cloudstack/usage/UsageTypes.java | 1 + test/integration/component/test_ss_volume_usage.py | 203 +++++++++++++++++++++ usage/src/com/cloud/usage/StorageTypes.java | 1 + usage/src/com/cloud/usage/UsageManagerImpl.java | 43 ++++- .../com/cloud/usage/parser/StorageUsageParser.java | 4 + 5 files changed, 251 insertions(+), 1 deletion(-) diff --git a/api/src/org/apache/cloudstack/usage/UsageTypes.java b/api/src/org/apache/cloudstack/usage/UsageTypes.java index d9cfc13..08cd7dd 100644 --- a/api/src/org/apache/cloudstack/usage/UsageTypes.java +++ b/api/src/org/apache/cloudstack/usage/UsageTypes.java @@ -42,6 +42,7 @@ public class UsageTypes { public static final int VM_DISK_BYTES_READ = 23; public static final int VM_DISK_BYTES_WRITE = 24; public static final int VM_SNAPSHOT = 25; + public static final int VOLUME_SECONDARY = 26; public static List<UsageTypeResponse> listUsageTypes() { List<UsageTypeResponse> responseList = new ArrayList<UsageTypeResponse>(); diff --git a/test/integration/component/test_ss_volume_usage.py b/test/integration/component/test_ss_volume_usage.py new file mode 100644 index 0000000..548f90f --- /dev/null +++ b/test/integration/component/test_ss_volume_usage.py @@ -0,0 +1,203 @@ +# 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. +""" Test cases for checking that the secondary Storage usage is accounted. This is verified by checking the usage_event table +for a volume in 'Uploaded' state. + This test case does the following: + 1.Creates an account and uploads a volume. + 2.After the volume is uploaded successfully, connects to the database + 3.From the database verifies that an entry is added to cloud.events table for the uploaded volume. + 4.Cleans up the resources. +""" + +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.lib.utils import * +from marvin.lib.base import * +from marvin.lib.common import * +from nose.plugins.attrib import attr +from marvin.sshClient import SshClient +from marvin.codes import (BACKED_UP, PASS, FAIL) +import time + + +def verify_vm(self, vmid, state): + list_vm = list_virtual_machines(self.userapiclient, + account=self.account.name, + domainid=self.account.domainid, + id=vmid + ) + self.assertEqual( + validateList(list_vm)[0], + PASS, + "Check List vm response for vmid: %s" % + vmid) + self.assertGreater( + len(list_vm), + 0, + "Check the list vm response for vm id: %s" % + vmid) + vm = list_vm[0] + self.assertEqual( + vm.id, + str(vmid), + "Vm deployed is different from the test") + self.assertEqual(vm.state, state, "VM is in %s state" %state) + + +def uploadVolume(self): + # upload a volume + self.debug("Upload volume format is '%s'" %self.uploadVolumeformat) + self.testdata["configurableData"]["upload_volume"]["format"] = self.uploadVolumeformat + self.testdata["configurableData"]["upload_volume"]["url"] = self.uploadvolumeUrl + upload_volume = Volume.upload( + self.apiclient, + self.testdata["configurableData"]["upload_volume"], + account=self.account.name, + domainid=self.domain.id, + zoneid=self.zone.id + ) + upload_volume.wait_for_upload(self.apiclient) + return upload_volume.id + +def restartUsageServer(self): + #Restart usage server + + sshClient = SshClient( + self.mgtSvrDetails["mgtSvrIp"], + 22, + self.mgtSvrDetails["user"], + self.mgtSvrDetails["passwd"] + ) + command = "service cloudstack-usage restart" + sshClient.execute(command) + return + +def checkUsage(self, uuid_upload_volume_id): + volume_id = self.dbclient.execute("SELECT id from cloud.volumes where uuid='%s';" % uuid_upload_volume_id) + self.debug("Volume id of uploaded volume is= %s" %volume_id[0]); + qryresult_after_usageServerExecution = self.dbclient.execute( + "SELECT type FROM cloud.usage_event where resource_id = '%s';" % (volume_id[0])) + self.debug("Usage Type is %s " % qryresult_after_usageServerExecution[0][0]) + self.assertEqual(qryresult_after_usageServerExecution[0][0], 'VOLUME.UPLOAD') + +class TestSecondaryVolumeUsage(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + testClient = super(TestSecondaryVolumeUsage, cls).getClsTestClient() + cls.apiclient = testClient.getApiClient() + cls.dbclient = testClient.getDbConnection() + cls.testdata = testClient.getParsedTestDataConfig() + cls.hypervisor = cls.testClient.getHypervisorInfo() + cls.storagetype = 'shared' + # Get Zone, Domain and templates + cls.domain = get_domain(cls.apiclient) + cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) + cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__ + cls._cleanup = [] + + # Create an account + cls.account = Account.create( + cls.apiclient, + cls.testdata["account"], + domainid=cls.domain.id + ) + cls._cleanup.append(cls.account) + + # Create user api client of the account + cls.userapiclient = testClient.getUserApiClient( + UserName=cls.account.name, + DomainName=cls.account.domain + ) + + # Create Service offering + cls.service_offering = ServiceOffering.create( + cls.apiclient, + cls.testdata["service_offering"], + ) + cls._cleanup.append(cls.service_offering) + + cls.disk_offering = DiskOffering.create( + cls.apiclient, + cls.testdata["disk_offering"], + ) + + cls._cleanup.append(cls.disk_offering) + + cls.skip = 0 + hosts = list_hosts( + cls.apiclient, + type="Routing" + ) + + for hypervisorhost in hosts: + if hypervisorhost.hypervisor.lower() in ["xenserver"]: + cls.uploadVolumeformat = "VHD" + cls.uploadvolumeUrl = "http://download.cloudstack.org/releases/2.0.0/systemvm.vhd.bz2" + break + elif hypervisorhost.hypervisor.lower() in ["vmware"]: + cls.uploadVolumeformat = "OVA" + cls.uploadvolumeUrl = "http://download.cloudstack.org/releases/2.2.0/systemvm-redundant-router.ova" + break + elif hypervisorhost.hypervisor == "KVM": + cls.uploadVolumeformat = "QCOW2" + cls.uploadvolumeUrl = "http://download.cloudstack.org/releases/2.0.0/UbuntuServer-10-04-64bit.qcow2.bz2" + break + elif hypervisorhost.hypervisor == "LXC": + cls.uploadvolumeformat = "QCOW2" + cls.uploadvolumeUrl = "http://download.cloudstack.org/releases/2.0.0/UbuntuServer-10-04-64bit.qcow2.bz2" + break + else: + break + + cls.template = get_template( + cls.apiclient, + cls.zone.id, + cls.testdata["ostype"]) + + try: + cls.vm = VirtualMachine.create( + cls.userapiclient, + cls.testdata["small"], + templateid=cls.template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + zoneid=cls.zone.id + ) + + except Exception as e: + cls.tearDownClass() + raise e + return + + @classmethod + def tearDownClass(cls): + try: + cleanup_resources(cls.apiclient, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + + @attr(tags=["basic", "advanced"], required_hardware="true") + def test_01_SecondaryUsageUploadedVolume(self): + try: + uploaded_volume_id_uuid = uploadVolume(self) + checkUsage(self, uploaded_volume_id_uuid) + except Exception as e: + self.tearDown() + raise e + return diff --git a/usage/src/com/cloud/usage/StorageTypes.java b/usage/src/com/cloud/usage/StorageTypes.java index a6f2131..9d4abdc 100644 --- a/usage/src/com/cloud/usage/StorageTypes.java +++ b/usage/src/com/cloud/usage/StorageTypes.java @@ -20,4 +20,5 @@ public class StorageTypes { public static final int TEMPLATE = 1; public static final int ISO = 2; public static final int SNAPSHOT = 3; + public static final int VOLUME = 4; } diff --git a/usage/src/com/cloud/usage/UsageManagerImpl.java b/usage/src/com/cloud/usage/UsageManagerImpl.java index 840b02c..4a60b24 100644 --- a/usage/src/com/cloud/usage/UsageManagerImpl.java +++ b/usage/src/com/cloud/usage/UsageManagerImpl.java @@ -985,7 +985,7 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna private boolean isVolumeEvent(String eventType) { return eventType != null && - (eventType.equals(EventTypes.EVENT_VOLUME_CREATE) || eventType.equals(EventTypes.EVENT_VOLUME_DELETE) || eventType.equals(EventTypes.EVENT_VOLUME_RESIZE)); + (eventType.equals(EventTypes.EVENT_VOLUME_CREATE) || eventType.equals(EventTypes.EVENT_VOLUME_DELETE) || eventType.equals(EventTypes.EVENT_VOLUME_RESIZE) || eventType.equals(EventTypes.EVENT_VOLUME_UPLOAD)); } private boolean isTemplateEvent(String eventType) { @@ -1390,6 +1390,21 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna long volId = event.getResourceId(); + if (EventTypes.EVENT_VOLUME_CREATE.equals(event.getType())) { + //For volumes which are 'attached' successfully, set the 'deleted' column in the usage_storage table, + //so that the secondary storage should stop accounting and only primary will be accounted. + SearchCriteria<UsageStorageVO> sc = _usageStorageDao.createSearchCriteria(); + sc.addAnd("id", SearchCriteria.Op.EQ, volId); + sc.addAnd("storageType", SearchCriteria.Op.EQ, StorageTypes.VOLUME); + List<UsageStorageVO> volumesVOs = _usageStorageDao.search(sc, null); + if (volumesVOs != null) { + if (volumesVOs.size() == 1) { + s_logger.debug("Setting the volume with id: " + volId + " to 'deleted' in the usage_storage table."); + volumesVOs.get(0).setDeleted(event.getCreateDate()); + _usageStorageDao.update(volumesVOs.get(0)); + } + } + } if (EventTypes.EVENT_VOLUME_CREATE.equals(event.getType()) || EventTypes.EVENT_VOLUME_RESIZE.equals(event.getType())) { SearchCriteria<UsageVolumeVO> sc = _usageVolumeDao.createSearchCriteria(); sc.addAnd("accountId", SearchCriteria.Op.EQ, event.getAccountId()); @@ -1430,6 +1445,32 @@ public class UsageManagerImpl extends ManagerBase implements UsageManager, Runna volumesVO.setDeleted(event.getCreateDate()); // there really shouldn't be more than one _usageVolumeDao.update(volumesVO); } + } else if (EventTypes.EVENT_VOLUME_UPLOAD.equals(event.getType())) { + //For Upload event add an entry to the usage_storage table. + SearchCriteria<UsageStorageVO> sc = _usageStorageDao.createSearchCriteria(); + sc.addAnd("accountId", SearchCriteria.Op.EQ, event.getAccountId()); + sc.addAnd("id", SearchCriteria.Op.EQ, volId); + sc.addAnd("deleted", SearchCriteria.Op.NULL); + List<UsageStorageVO> volumesVOs = _usageStorageDao.search(sc, null); + + if (volumesVOs.size() > 0) { + //This is a safeguard to avoid double counting of volumes. + s_logger.error("Found duplicate usage entry for volume: " + volId + " assigned to account: " + event.getAccountId() + "; marking as deleted..."); + } + for (UsageStorageVO volumesVO : volumesVOs) { + if (s_logger.isDebugEnabled()) { + s_logger.debug("deleting volume: " + volumesVO.getId() + " from account: " + volumesVO.getAccountId()); + } + volumesVO.setDeleted(event.getCreateDate()); + _usageStorageDao.update(volumesVO); + } + + if (s_logger.isDebugEnabled()) { + s_logger.debug("create volume with id : " + volId + " for account: " + event.getAccountId()); + } + Account acct = _accountDao.findByIdIncludingRemoved(event.getAccountId()); + UsageStorageVO volumeVO = new UsageStorageVO(volId, event.getZoneId(), event.getAccountId(), acct.getDomainId(), StorageTypes.VOLUME, event.getTemplateId(), event.getSize(), event.getCreateDate(), null); + _usageStorageDao.persist(volumeVO); } } diff --git a/usage/src/com/cloud/usage/parser/StorageUsageParser.java b/usage/src/com/cloud/usage/parser/StorageUsageParser.java index 03aa97b..8231756 100644 --- a/usage/src/com/cloud/usage/parser/StorageUsageParser.java +++ b/usage/src/com/cloud/usage/parser/StorageUsageParser.java @@ -180,6 +180,10 @@ public class StorageUsageParser { usage_type = UsageTypes.SNAPSHOT; usageDesc += "Snapshot "; break; + case StorageTypes.VOLUME: + usage_type = UsageTypes.VOLUME_SECONDARY; + usageDesc += "Volume "; + break; } //Create the usage record usageDesc += "Id:" + storageId + " Size:" + size; -- To stop receiving notification emails like this one, please contact ['"commits@cloudstack.apache.org" <commits@cloudstack.apache.org>'].