mirror of https://github.com/apache/cloudstack.git
283 lines
11 KiB
Java
283 lines
11 KiB
Java
// 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
|
|
// 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.
|
|
package com.cloud.usage;
|
|
|
|
import java.io.BufferedReader;
|
|
import java.io.BufferedWriter;
|
|
import java.io.FileReader;
|
|
import java.io.FileWriter;
|
|
import java.io.IOException;
|
|
import java.sql.Connection;
|
|
import java.sql.PreparedStatement;
|
|
import java.sql.ResultSet;
|
|
import java.sql.SQLException;
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
|
|
import com.cloud.utils.exception.CloudRuntimeException;
|
|
import org.apache.log4j.Logger;
|
|
|
|
import com.cloud.utils.db.TransactionLegacy;
|
|
|
|
/**
|
|
* This class must not be used concurrently because its state changes often during
|
|
* execution in a non synchronized way
|
|
*/
|
|
public class UsageSanityChecker {
|
|
|
|
protected static final Logger s_logger = Logger.getLogger(UsageSanityChecker.class);
|
|
protected static final int DEFAULT_AGGREGATION_RANGE = 1440;
|
|
protected StringBuilder errors;
|
|
protected List<CheckCase> checkCases;
|
|
protected String lastCheckFile = "/usr/local/libexec/sanity-check-last-id";
|
|
protected String lastCheckId = "";
|
|
protected int lastId = -1;
|
|
protected int maxId = -1;
|
|
protected Connection conn;
|
|
|
|
protected void reset() {
|
|
errors = new StringBuilder();
|
|
checkCases = new ArrayList<CheckCase>();
|
|
}
|
|
|
|
protected boolean checkItemCountByPstmt() throws SQLException {
|
|
boolean checkOk = true;
|
|
|
|
for(CheckCase check : checkCases) {
|
|
checkOk &= checkItemCountByPstmt(check);
|
|
}
|
|
|
|
return checkOk;
|
|
}
|
|
|
|
protected boolean checkItemCountByPstmt(CheckCase checkCase) throws SQLException {
|
|
boolean checkOk = true;
|
|
/*
|
|
* Check for item usage records which are created after it is removed
|
|
*/
|
|
try (PreparedStatement pstmt = conn.prepareStatement(checkCase.sqlTemplate)) {
|
|
if(checkCase.checkId) {
|
|
pstmt.setInt(1, lastId);
|
|
pstmt.setInt(2, maxId);
|
|
}
|
|
try(ResultSet rs = pstmt.executeQuery();) {
|
|
if (rs.next() && (rs.getInt(1) > 0)) {
|
|
errors.append(String.format("Error: Found %s %s\n", rs.getInt(1), checkCase.itemName));
|
|
checkOk = false;
|
|
}
|
|
}catch (Exception e)
|
|
{
|
|
s_logger.error("checkItemCountByPstmt:Exception:"+e.getMessage());
|
|
throw new CloudRuntimeException("checkItemCountByPstmt:Exception:"+e.getMessage(),e);
|
|
}
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
s_logger.error("checkItemCountByPstmt:Exception:"+e.getMessage());
|
|
throw new CloudRuntimeException("checkItemCountByPstmt:Exception:"+e.getMessage(),e);
|
|
}
|
|
return checkOk;
|
|
}
|
|
|
|
protected void checkMaxUsage() throws SQLException {
|
|
int aggregationRange = DEFAULT_AGGREGATION_RANGE;
|
|
try (PreparedStatement pstmt = conn.prepareStatement(
|
|
"SELECT value FROM `cloud`.`configuration` where name = 'usage.stats.job.aggregation.range'");)
|
|
{
|
|
try(ResultSet rs = pstmt.executeQuery();) {
|
|
if (rs.next()) {
|
|
aggregationRange = rs.getInt(1);
|
|
} else {
|
|
s_logger.debug("Failed to retrieve aggregation range. Using default : " + aggregationRange);
|
|
}
|
|
}catch (SQLException e) {
|
|
s_logger.error("checkMaxUsage:Exception:"+e.getMessage());
|
|
throw new CloudRuntimeException("checkMaxUsage:Exception:"+e.getMessage());
|
|
}
|
|
} catch (SQLException e) {
|
|
s_logger.error("checkMaxUsage:Exception:"+e.getMessage());
|
|
throw new CloudRuntimeException("checkMaxUsage:Exception:"+e.getMessage());
|
|
}
|
|
int aggregationHours = aggregationRange / 60;
|
|
|
|
addCheckCase("SELECT count(*) FROM `cloud_usage`.`cloud_usage` cu where usage_type not in (4,5) and raw_usage > "
|
|
+ aggregationHours,
|
|
"usage records with raw_usage > " + aggregationHours,
|
|
lastCheckId);
|
|
}
|
|
|
|
protected void checkVmUsage() {
|
|
addCheckCase("select count(*) from cloud_usage.cloud_usage cu inner join cloud.vm_instance vm "
|
|
+ "where vm.type = 'User' and cu.usage_type in (1 , 2) "
|
|
+ "and cu.usage_id = vm.id and cu.start_date > vm.removed ",
|
|
"Vm usage records which are created after Vm is destroyed",
|
|
lastCheckId);
|
|
|
|
addCheckCase("select sum(cnt) from (select count(*) as cnt from cloud_usage.usage_vm_instance "
|
|
+ "where usage_type =1 and end_date is null group by vm_instance_id "
|
|
+ "having count(vm_instance_id) > 1) c ;",
|
|
"duplicate running Vm entries in vm usage helper table");
|
|
|
|
addCheckCase("select sum(cnt) from (select count(*) as cnt from cloud_usage.usage_vm_instance "
|
|
+ "where usage_type =2 and end_date is null group by vm_instance_id "
|
|
+ "having count(vm_instance_id) > 1) c ;",
|
|
"duplicate allocated Vm entries in vm usage helper table");
|
|
|
|
addCheckCase("select count(vm_instance_id) from cloud_usage.usage_vm_instance o "
|
|
+ "where o.end_date is null and o.usage_type=1 and not exists "
|
|
+ "(select 1 from cloud_usage.usage_vm_instance i where "
|
|
+ "i.vm_instance_id=o.vm_instance_id and usage_type=2 and i.end_date is null)",
|
|
"running Vm entries without corresponding allocated entries in vm usage helper table");
|
|
}
|
|
|
|
protected void checkVolumeUsage() {
|
|
addCheckCase("select count(*) from cloud_usage.cloud_usage cu inner join cloud.volumes v where "
|
|
+ "cu.usage_type = 6 and cu.usage_id = v.id and cu.start_date > v.removed ",
|
|
"volume usage records which are created after volume is removed",
|
|
lastCheckId);
|
|
|
|
addCheckCase("select sum(cnt) from (select count(*) as cnt from cloud_usage.usage_volume "
|
|
+ "where deleted is null group by id having count(id) > 1) c;",
|
|
"duplicate records in volume usage helper table");
|
|
}
|
|
|
|
protected void checkTemplateISOUsage() {
|
|
addCheckCase("select count(*) from cloud_usage.cloud_usage cu inner join cloud.template_zone_ref tzr where "
|
|
+ "cu.usage_id = tzr.template_id and cu.zone_id = tzr.zone_id and cu.usage_type in (7,8) and cu.start_date > tzr.removed ",
|
|
"template/ISO usage records which are created after it is removed",
|
|
lastCheckId);
|
|
}
|
|
|
|
protected void checkSnapshotUsage() {
|
|
addCheckCase("select count(*) from cloud_usage.cloud_usage cu inner join cloud.snapshots s where "
|
|
+ "cu.usage_id = s.id and cu.usage_type = 9 and cu.start_date > s.removed ",
|
|
"snapshot usage records which are created after it is removed",
|
|
lastCheckId);
|
|
}
|
|
|
|
protected void readLastCheckId(){
|
|
try(BufferedReader reader = new BufferedReader(new FileReader(lastCheckFile));) {
|
|
String lastIdText = null;
|
|
lastId = -1;
|
|
if ((reader != null) && (lastIdText = reader.readLine()) != null) {
|
|
lastId = Integer.parseInt(lastIdText);
|
|
}
|
|
} catch (Exception e) {
|
|
s_logger.error("readLastCheckId:Exception:"+e.getMessage(),e);
|
|
}
|
|
}
|
|
|
|
protected void readMaxId() throws SQLException {
|
|
try (PreparedStatement pstmt = conn.prepareStatement("select max(id) from cloud_usage.cloud_usage");
|
|
ResultSet rs = pstmt.executeQuery();)
|
|
{
|
|
maxId = -1;
|
|
if (rs.next() && (rs.getInt(1) > 0)) {
|
|
maxId = rs.getInt(1);
|
|
lastCheckId += " and cu.id <= ?";
|
|
}
|
|
}catch (Exception e) {
|
|
s_logger.error("readMaxId:"+e.getMessage(),e);
|
|
}
|
|
}
|
|
|
|
protected void updateNewMaxId() {
|
|
try (FileWriter fstream = new FileWriter(lastCheckFile);
|
|
BufferedWriter out = new BufferedWriter(fstream);
|
|
){
|
|
out.write("" + maxId);
|
|
} catch (IOException e) {
|
|
s_logger.error("updateNewMaxId:Exception:"+e.getMessage());
|
|
// Error while writing last check id
|
|
}
|
|
}
|
|
|
|
public String runSanityCheck() throws SQLException {
|
|
|
|
readLastCheckId();
|
|
if (lastId > 0) {
|
|
lastCheckId = " and cu.id > ?";
|
|
}
|
|
|
|
conn = getConnection();
|
|
readMaxId();
|
|
|
|
reset();
|
|
|
|
checkMaxUsage();
|
|
checkVmUsage();
|
|
checkVolumeUsage();
|
|
checkTemplateISOUsage();
|
|
checkSnapshotUsage();
|
|
|
|
checkItemCountByPstmt();
|
|
|
|
return errors.toString();
|
|
}
|
|
|
|
/**
|
|
* Local acquisition of {@link Connection} to remove static cling
|
|
* @return
|
|
*/
|
|
protected Connection getConnection() {
|
|
return TransactionLegacy.getStandaloneConnection();
|
|
}
|
|
|
|
public static void main(String args[]) {
|
|
UsageSanityChecker usc = new UsageSanityChecker();
|
|
String sanityErrors;
|
|
try {
|
|
sanityErrors = usc.runSanityCheck();
|
|
if (sanityErrors.length() > 0) {
|
|
s_logger.error(sanityErrors.toString());
|
|
}
|
|
} catch (SQLException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
protected void addCheckCase(String sqlTemplate, String itemName, String lastCheckId) {
|
|
checkCases.add(new CheckCase(sqlTemplate, itemName, lastCheckId));
|
|
}
|
|
|
|
protected void addCheckCase(String sqlTemplate, String itemName) {
|
|
checkCases.add(new CheckCase(sqlTemplate, itemName));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Just an abstraction of the kind of check to repeat across these cases
|
|
* encapsulating what change for each specific case
|
|
*/
|
|
class CheckCase {
|
|
public String sqlTemplate;
|
|
public String itemName;
|
|
public boolean checkId = false;
|
|
|
|
public CheckCase(String sqlTemplate, String itemName, String lastCheckId) {
|
|
checkId = true;
|
|
this.sqlTemplate = sqlTemplate + lastCheckId;
|
|
this.itemName = itemName;
|
|
}
|
|
|
|
public CheckCase(String sqlTemplate, String itemName) {
|
|
checkId = false;
|
|
this.sqlTemplate = sqlTemplate;
|
|
this.itemName = itemName;
|
|
}
|
|
} |