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>'].

Reply via email to