// 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; } }