Skip to content

Commit 3e2ef19

Browse files
PranaliMyadvr
authored andcommitted
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.
1 parent 290a8bc commit 3e2ef19

5 files changed

Lines changed: 251 additions & 1 deletion

File tree

api/src/org/apache/cloudstack/usage/UsageTypes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ public class UsageTypes {
4242
public static final int VM_DISK_BYTES_READ = 23;
4343
public static final int VM_DISK_BYTES_WRITE = 24;
4444
public static final int VM_SNAPSHOT = 25;
45+
public static final int VOLUME_SECONDARY = 26;
4546

4647
public static List<UsageTypeResponse> listUsageTypes() {
4748
List<UsageTypeResponse> responseList = new ArrayList<UsageTypeResponse>();
Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
""" Test cases for checking that the secondary Storage usage is accounted. This is verified by checking the usage_event table
18+
for a volume in 'Uploaded' state.
19+
This test case does the following:
20+
1.Creates an account and uploads a volume.
21+
2.After the volume is uploaded successfully, connects to the database
22+
3.From the database verifies that an entry is added to cloud.events table for the uploaded volume.
23+
4.Cleans up the resources.
24+
"""
25+
26+
from marvin.cloudstackTestCase import *
27+
from marvin.cloudstackAPI import *
28+
from marvin.lib.utils import *
29+
from marvin.lib.base import *
30+
from marvin.lib.common import *
31+
from nose.plugins.attrib import attr
32+
from marvin.sshClient import SshClient
33+
from marvin.codes import (BACKED_UP, PASS, FAIL)
34+
import time
35+
36+
37+
def verify_vm(self, vmid, state):
38+
list_vm = list_virtual_machines(self.userapiclient,
39+
account=self.account.name,
40+
domainid=self.account.domainid,
41+
id=vmid
42+
)
43+
self.assertEqual(
44+
validateList(list_vm)[0],
45+
PASS,
46+
"Check List vm response for vmid: %s" %
47+
vmid)
48+
self.assertGreater(
49+
len(list_vm),
50+
0,
51+
"Check the list vm response for vm id: %s" %
52+
vmid)
53+
vm = list_vm[0]
54+
self.assertEqual(
55+
vm.id,
56+
str(vmid),
57+
"Vm deployed is different from the test")
58+
self.assertEqual(vm.state, state, "VM is in %s state" %state)
59+
60+
61+
def uploadVolume(self):
62+
# upload a volume
63+
self.debug("Upload volume format is '%s'" %self.uploadVolumeformat)
64+
self.testdata["configurableData"]["upload_volume"]["format"] = self.uploadVolumeformat
65+
self.testdata["configurableData"]["upload_volume"]["url"] = self.uploadvolumeUrl
66+
upload_volume = Volume.upload(
67+
self.apiclient,
68+
self.testdata["configurableData"]["upload_volume"],
69+
account=self.account.name,
70+
domainid=self.domain.id,
71+
zoneid=self.zone.id
72+
)
73+
upload_volume.wait_for_upload(self.apiclient)
74+
return upload_volume.id
75+
76+
def restartUsageServer(self):
77+
#Restart usage server
78+
79+
sshClient = SshClient(
80+
self.mgtSvrDetails["mgtSvrIp"],
81+
22,
82+
self.mgtSvrDetails["user"],
83+
self.mgtSvrDetails["passwd"]
84+
)
85+
command = "service cloudstack-usage restart"
86+
sshClient.execute(command)
87+
return
88+
89+
def checkUsage(self, uuid_upload_volume_id):
90+
volume_id = self.dbclient.execute("SELECT id from cloud.volumes where uuid='%s';" % uuid_upload_volume_id)
91+
self.debug("Volume id of uploaded volume is= %s" %volume_id[0]);
92+
qryresult_after_usageServerExecution = self.dbclient.execute(
93+
"SELECT type FROM cloud.usage_event where resource_id = '%s';" % (volume_id[0]))
94+
self.debug("Usage Type is %s " % qryresult_after_usageServerExecution[0][0])
95+
self.assertEqual(qryresult_after_usageServerExecution[0][0], 'VOLUME.UPLOAD')
96+
97+
class TestSecondaryVolumeUsage(cloudstackTestCase):
98+
99+
@classmethod
100+
def setUpClass(cls):
101+
testClient = super(TestSecondaryVolumeUsage, cls).getClsTestClient()
102+
cls.apiclient = testClient.getApiClient()
103+
cls.dbclient = testClient.getDbConnection()
104+
cls.testdata = testClient.getParsedTestDataConfig()
105+
cls.hypervisor = cls.testClient.getHypervisorInfo()
106+
cls.storagetype = 'shared'
107+
# Get Zone, Domain and templates
108+
cls.domain = get_domain(cls.apiclient)
109+
cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests())
110+
cls.mgtSvrDetails = cls.config.__dict__["mgtSvr"][0].__dict__
111+
cls._cleanup = []
112+
113+
# Create an account
114+
cls.account = Account.create(
115+
cls.apiclient,
116+
cls.testdata["account"],
117+
domainid=cls.domain.id
118+
)
119+
cls._cleanup.append(cls.account)
120+
121+
# Create user api client of the account
122+
cls.userapiclient = testClient.getUserApiClient(
123+
UserName=cls.account.name,
124+
DomainName=cls.account.domain
125+
)
126+
127+
# Create Service offering
128+
cls.service_offering = ServiceOffering.create(
129+
cls.apiclient,
130+
cls.testdata["service_offering"],
131+
)
132+
cls._cleanup.append(cls.service_offering)
133+
134+
cls.disk_offering = DiskOffering.create(
135+
cls.apiclient,
136+
cls.testdata["disk_offering"],
137+
)
138+
139+
cls._cleanup.append(cls.disk_offering)
140+
141+
cls.skip = 0
142+
hosts = list_hosts(
143+
cls.apiclient,
144+
type="Routing"
145+
)
146+
147+
for hypervisorhost in hosts:
148+
if hypervisorhost.hypervisor.lower() in ["xenserver"]:
149+
cls.uploadVolumeformat = "VHD"
150+
cls.uploadvolumeUrl = "http://download.cloudstack.org/releases/2.0.0/systemvm.vhd.bz2"
151+
break
152+
elif hypervisorhost.hypervisor.lower() in ["vmware"]:
153+
cls.uploadVolumeformat = "OVA"
154+
cls.uploadvolumeUrl = "http://download.cloudstack.org/releases/2.2.0/systemvm-redundant-router.ova"
155+
break
156+
elif hypervisorhost.hypervisor == "KVM":
157+
cls.uploadVolumeformat = "QCOW2"
158+
cls.uploadvolumeUrl = "http://download.cloudstack.org/releases/2.0.0/UbuntuServer-10-04-64bit.qcow2.bz2"
159+
break
160+
elif hypervisorhost.hypervisor == "LXC":
161+
cls.uploadvolumeformat = "QCOW2"
162+
cls.uploadvolumeUrl = "http://download.cloudstack.org/releases/2.0.0/UbuntuServer-10-04-64bit.qcow2.bz2"
163+
break
164+
else:
165+
break
166+
167+
cls.template = get_template(
168+
cls.apiclient,
169+
cls.zone.id,
170+
cls.testdata["ostype"])
171+
172+
try:
173+
cls.vm = VirtualMachine.create(
174+
cls.userapiclient,
175+
cls.testdata["small"],
176+
templateid=cls.template.id,
177+
accountid=cls.account.name,
178+
domainid=cls.account.domainid,
179+
serviceofferingid=cls.service_offering.id,
180+
zoneid=cls.zone.id
181+
)
182+
183+
except Exception as e:
184+
cls.tearDownClass()
185+
raise e
186+
return
187+
188+
@classmethod
189+
def tearDownClass(cls):
190+
try:
191+
cleanup_resources(cls.apiclient, cls._cleanup)
192+
except Exception as e:
193+
raise Exception("Warning: Exception during cleanup : %s" % e)
194+
195+
@attr(tags=["basic", "advanced"], required_hardware="true")
196+
def test_01_SecondaryUsageUploadedVolume(self):
197+
try:
198+
uploaded_volume_id_uuid = uploadVolume(self)
199+
checkUsage(self, uploaded_volume_id_uuid)
200+
except Exception as e:
201+
self.tearDown()
202+
raise e
203+
return

usage/src/com/cloud/usage/StorageTypes.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,5 @@ public class StorageTypes {
2020
public static final int TEMPLATE = 1;
2121
public static final int ISO = 2;
2222
public static final int SNAPSHOT = 3;
23+
public static final int VOLUME = 4;
2324
}

usage/src/com/cloud/usage/UsageManagerImpl.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,7 @@ private boolean isIPEvent(String eventType) {
985985

986986
private boolean isVolumeEvent(String eventType) {
987987
return eventType != null &&
988-
(eventType.equals(EventTypes.EVENT_VOLUME_CREATE) || eventType.equals(EventTypes.EVENT_VOLUME_DELETE) || eventType.equals(EventTypes.EVENT_VOLUME_RESIZE));
988+
(eventType.equals(EventTypes.EVENT_VOLUME_CREATE) || eventType.equals(EventTypes.EVENT_VOLUME_DELETE) || eventType.equals(EventTypes.EVENT_VOLUME_RESIZE) || eventType.equals(EventTypes.EVENT_VOLUME_UPLOAD));
989989
}
990990

991991
private boolean isTemplateEvent(String eventType) {
@@ -1390,6 +1390,21 @@ private void createVolumeHelperEvent(UsageEventVO event) {
13901390

13911391
long volId = event.getResourceId();
13921392

1393+
if (EventTypes.EVENT_VOLUME_CREATE.equals(event.getType())) {
1394+
//For volumes which are 'attached' successfully, set the 'deleted' column in the usage_storage table,
1395+
//so that the secondary storage should stop accounting and only primary will be accounted.
1396+
SearchCriteria<UsageStorageVO> sc = _usageStorageDao.createSearchCriteria();
1397+
sc.addAnd("id", SearchCriteria.Op.EQ, volId);
1398+
sc.addAnd("storageType", SearchCriteria.Op.EQ, StorageTypes.VOLUME);
1399+
List<UsageStorageVO> volumesVOs = _usageStorageDao.search(sc, null);
1400+
if (volumesVOs != null) {
1401+
if (volumesVOs.size() == 1) {
1402+
s_logger.debug("Setting the volume with id: " + volId + " to 'deleted' in the usage_storage table.");
1403+
volumesVOs.get(0).setDeleted(event.getCreateDate());
1404+
_usageStorageDao.update(volumesVOs.get(0));
1405+
}
1406+
}
1407+
}
13931408
if (EventTypes.EVENT_VOLUME_CREATE.equals(event.getType()) || EventTypes.EVENT_VOLUME_RESIZE.equals(event.getType())) {
13941409
SearchCriteria<UsageVolumeVO> sc = _usageVolumeDao.createSearchCriteria();
13951410
sc.addAnd("accountId", SearchCriteria.Op.EQ, event.getAccountId());
@@ -1430,6 +1445,32 @@ private void createVolumeHelperEvent(UsageEventVO event) {
14301445
volumesVO.setDeleted(event.getCreateDate()); // there really shouldn't be more than one
14311446
_usageVolumeDao.update(volumesVO);
14321447
}
1448+
} else if (EventTypes.EVENT_VOLUME_UPLOAD.equals(event.getType())) {
1449+
//For Upload event add an entry to the usage_storage table.
1450+
SearchCriteria<UsageStorageVO> sc = _usageStorageDao.createSearchCriteria();
1451+
sc.addAnd("accountId", SearchCriteria.Op.EQ, event.getAccountId());
1452+
sc.addAnd("id", SearchCriteria.Op.EQ, volId);
1453+
sc.addAnd("deleted", SearchCriteria.Op.NULL);
1454+
List<UsageStorageVO> volumesVOs = _usageStorageDao.search(sc, null);
1455+
1456+
if (volumesVOs.size() > 0) {
1457+
//This is a safeguard to avoid double counting of volumes.
1458+
s_logger.error("Found duplicate usage entry for volume: " + volId + " assigned to account: " + event.getAccountId() + "; marking as deleted...");
1459+
}
1460+
for (UsageStorageVO volumesVO : volumesVOs) {
1461+
if (s_logger.isDebugEnabled()) {
1462+
s_logger.debug("deleting volume: " + volumesVO.getId() + " from account: " + volumesVO.getAccountId());
1463+
}
1464+
volumesVO.setDeleted(event.getCreateDate());
1465+
_usageStorageDao.update(volumesVO);
1466+
}
1467+
1468+
if (s_logger.isDebugEnabled()) {
1469+
s_logger.debug("create volume with id : " + volId + " for account: " + event.getAccountId());
1470+
}
1471+
Account acct = _accountDao.findByIdIncludingRemoved(event.getAccountId());
1472+
UsageStorageVO volumeVO = new UsageStorageVO(volId, event.getZoneId(), event.getAccountId(), acct.getDomainId(), StorageTypes.VOLUME, event.getTemplateId(), event.getSize(), event.getCreateDate(), null);
1473+
_usageStorageDao.persist(volumeVO);
14331474
}
14341475
}
14351476

usage/src/com/cloud/usage/parser/StorageUsageParser.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,10 @@ private static void createUsageRecord(long zoneId, int type, long runningTime, D
180180
usage_type = UsageTypes.SNAPSHOT;
181181
usageDesc += "Snapshot ";
182182
break;
183+
case StorageTypes.VOLUME:
184+
usage_type = UsageTypes.VOLUME_SECONDARY;
185+
usageDesc += "Volume ";
186+
break;
183187
}
184188
//Create the usage record
185189
usageDesc += "Id:" + storageId + " Size:" + size;

0 commit comments

Comments
 (0)