// 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. package com.cloud.upgrade.dao; import com.cloud.utils.PropertiesUtil; import com.cloud.utils.db.ScriptRunner; import com.cloud.utils.exception.CloudRuntimeException; import com.cloud.utils.script.Script; import org.apache.cloudstack.acl.RoleType; import org.apache.log4j.Logger; import java.io.File; import java.io.FileReader; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.Map; public class Upgrade481to490 implements DbUpgrade { final static Logger s_logger = Logger.getLogger(Upgrade481to490.class); @Override public String[] getUpgradableVersionRange() { return new String[] {"4.8.1", "4.9.0"}; } @Override public String getUpgradedVersion() { return "4.9.0"; } @Override public boolean supportsRollingUpgrade() { return false; } @Override public File[] getPrepareScripts() { String script = Script.findScript("", "db/schema-481to490.sql"); if (script == null) { throw new CloudRuntimeException("Unable to find db/schema-481to490.sql"); } return new File[] {new File(script)}; } @Override public void performDataMigration(Connection conn) { setupRolesAndPermissionsForDynamicChecker(conn); } private void migrateAccountsToDefaultRoles(final Connection conn) { try (final PreparedStatement selectStatement = conn.prepareStatement("SELECT `id`, `type` FROM `cloud`.`account`;"); final ResultSet selectResultSet = selectStatement.executeQuery()) { while (selectResultSet.next()) { final Long accountId = selectResultSet.getLong(1); final Short accountType = selectResultSet.getShort(2); final Long roleId = RoleType.getByAccountType(accountType).getId(); if (roleId < 1L || roleId > 4L) { s_logger.warn("Skipping role ID migration due to invalid role_id resolved for account id=" + accountId); continue; } try (final PreparedStatement updateStatement = conn.prepareStatement("UPDATE `cloud`.`account` SET account.role_id = ? WHERE account.id = ? ;")) { updateStatement.setLong(1, roleId); updateStatement.setLong(2, accountId); updateStatement.executeUpdate(); } catch (SQLException e) { s_logger.error("Failed to update cloud.account role_id for account id:" + accountId + " with exception: " + e.getMessage()); throw new CloudRuntimeException("Exception while updating cloud.account role_id", e); } } } catch (SQLException e) { throw new CloudRuntimeException("Exception while migrating existing account table's role_id column to a role based on account type", e); } s_logger.debug("Done migrating existing accounts to use one of default roles based on account type"); } private void setupRolesAndPermissionsForDynamicChecker(final Connection conn) { final String alterTableSql = "ALTER TABLE `cloud`.`account` " + "ADD COLUMN `role_id` bigint(20) unsigned COMMENT 'role id for this account' AFTER `type`, " + "ADD KEY `fk_account__role_id` (`role_id`), " + "ADD CONSTRAINT `fk_account__role_id` FOREIGN KEY (`role_id`) REFERENCES `roles` (`id`);"; try (final PreparedStatement pstmt = conn.prepareStatement(alterTableSql)) { pstmt.executeUpdate(); } catch (SQLException e) { if (e.getMessage().contains("role_id")) { s_logger.warn("cloud.account table already has the role_id column, skipping altering table and migration of accounts"); return; } else { throw new CloudRuntimeException("Unable to create column role_id in table cloud.account", e); } } try (final PreparedStatement pstmt = conn.prepareStatement("ALTER TABLE `cloud_usage`.`account` ADD COLUMN `role_id` bigint(20) unsigned AFTER `type`")) { pstmt.executeUpdate(); } catch (SQLException e) { throw new CloudRuntimeException("Unable to create column role_id in table cloud_usage.account", e); } migrateAccountsToDefaultRoles(conn); final Map<String, String> apiMap = PropertiesUtil.processConfigFile(new String[] { PropertiesUtil.getDefaultApiCommandsFileName() }); if (apiMap == null || apiMap.isEmpty()) { if (s_logger.isDebugEnabled()) { s_logger.debug("The commands.properties file and default role permissions were not found. " + "Assuming new installation, configuring default role-api mappings."); } String script = Script.findScript("", "db/create-default-role-api-mappings.sql"); if (script == null) { s_logger.error("Unable to find default role-api mapping sql file, please configure api per role manually"); return; } try(final FileReader reader = new FileReader(new File(script))) { ScriptRunner runner = new ScriptRunner(conn, false, true); runner.runScript(reader); } catch (SQLException | IOException e) { s_logger.error("Unable to insert default api-role mappings from file: " + script + ". Please configure api per role manually, giving up!", e); } } } @Override public File[] getCleanupScripts() { String script = Script.findScript("", "db/schema-481to490-cleanup.sql"); if (script == null) { throw new CloudRuntimeException("Unable to find db/schema-481to490-cleanup.sql"); } return new File[] {new File(script)}; } }