diff options
Diffstat (limited to 'appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src')
16 files changed, 1474 insertions, 0 deletions
diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/inmemory/LockManagerInMemoryImpl.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/inmemory/LockManagerInMemoryImpl.java new file mode 100644 index 000000000..309e2790c --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/inmemory/LockManagerInMemoryImpl.java @@ -0,0 +1,130 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.inmemory; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.openecomp.appc.lockmanager.api.LockException; +import org.openecomp.appc.lockmanager.api.LockManager; +import com.att.eelf.configuration.EELFLogger; +import com.att.eelf.configuration.EELFManager; + + +public class LockManagerInMemoryImpl implements LockManager { + + private static LockManagerInMemoryImpl instance = null; + private Map<String, LockValue> lockedVNFs; + + private static final EELFLogger debugLogger = EELFManager.getInstance().getDebugLogger(); + + private LockManagerInMemoryImpl() { + lockedVNFs = new ConcurrentHashMap<>(); + } + + public static LockManager getLockManager() { + if(instance == null) { + instance = new LockManagerInMemoryImpl(); + } + return instance; + } + + @Override + public boolean acquireLock(String resource, String owner) throws LockException { + return acquireLock(resource, owner, 0); + } + + @Override + public boolean acquireLock(String resource, String owner, long timeout) throws LockException { + debugLogger.debug("Try to acquire lock on resource " + resource + " with owner " + owner); + long now = System.currentTimeMillis(); + LockValue lockValue = lockedVNFs.get(resource); + if (lockValue != null) { + if (lockIsMine(lockValue, owner, now) || hasExpired(lockValue, now)) { + setExpirationTime(resource, owner, timeout, now); + debugLogger.debug("Locked successfully resource " + resource + " with owner " + owner + " for " + timeout + " ms"); + return hasExpired(lockValue, now); + } + else { + debugLogger.debug("Owner " + owner + " tried to lock resource " + resource + " but it is already locked by owner " + lockValue.getOwner()); + throw new LockException("Owner " + owner + " tried to lock resource " + resource + " but it is already locked by owner " + lockValue.getOwner()); + } + } + else { + setExpirationTime(resource, owner, timeout, now); + debugLogger.debug("Locked successfully resource " + resource + " with owner " + owner + " for " + timeout + " ms"); + return true; + } + } + + @Override + public void releaseLock(String resource, String owner) throws LockException { + debugLogger.debug("Try to release lock on resource " + resource + " with owner " + owner); + long now = System.currentTimeMillis(); + LockValue lockValue = lockedVNFs.get(resource); + if (lockValue != null) { + if (!hasExpired(lockValue, now)) { + if (isOwner(lockValue, owner)) { + debugLogger.debug("Unlocked successfully resource " + resource + " with owner " + owner); + lockedVNFs.remove(resource); + } + else { + debugLogger.debug("Unlock failed. Tried to release lock on resource " + resource + " from owner " + owner + " but it is held by a different owner"); + throw new LockException("Unlock failed. Tried to release lock on resource " + resource + " from owner " + owner + " but it is held by a different owner"); + } + } + else { + lockedVNFs.remove(resource); + debugLogger.debug("Unlock failed. lock on resource " + resource + " has expired"); + throw new LockException("Unlock failed. lock on resource " + resource + " has expired"); + } + + } + else { + debugLogger.debug("Tried to release lock on resource " + resource + " from owner " + owner + " but there is not lock on this resource"); + throw new LockException("Tried to release lock on resource " + resource + " from owner " + owner + " but there is not lock on this resource"); + } + } + + @Override + public boolean isLocked(String resource) { + return lockedVNFs.get(resource)!=null?true:false; + } + + private boolean lockIsMine(LockValue lockValue, String owner, long now) { + return isOwner(lockValue, owner) && !hasExpired(lockValue, now); + } + + private boolean isOwner(LockValue lockValue, String owner) { + return lockValue.getOwner() != null && lockValue.getOwner().equals(owner); + } + + private boolean hasExpired(LockValue lockValue, long now) { + return (lockValue.getExpirationTime() != 0 && now > lockValue.getExpirationTime()); + } + + private void setExpirationTime(String resource, String owner, long timeout, long now) { + long expirationTime = timeout == 0 ? 0 : now + timeout; + LockValue lockValue = new LockValue(owner, expirationTime); + lockedVNFs.put(resource, lockValue); + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/inmemory/LockValue.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/inmemory/LockValue.java new file mode 100644 index 000000000..abdd494d9 --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/inmemory/LockValue.java @@ -0,0 +1,43 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.inmemory; + + +public class LockValue { + + private String owner; + private long expirationTime; + + LockValue(String owner, long expirationTime) { + this.owner = owner; + this.expirationTime = expirationTime; + } + + String getOwner() { + return owner; + } + + long getExpirationTime() { + return expirationTime; + } + +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/JdbcLockManager.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/JdbcLockManager.java new file mode 100644 index 000000000..97a5cc339 --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/JdbcLockManager.java @@ -0,0 +1,51 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql; + +import java.sql.Connection; + +import org.openecomp.appc.dao.util.JdbcConnectionFactory; +import org.openecomp.appc.lockmanager.api.LockManager; + +public abstract class JdbcLockManager implements LockManager { + + private static final String DEF_TABLE_LOCK_MANAGEMENT = "LOCK_MANAGEMENT"; + + private JdbcConnectionFactory connectionFactory; + protected String tableName = DEF_TABLE_LOCK_MANAGEMENT; + + public void setConnectionFactory(JdbcConnectionFactory connectionFactory) { + this.connectionFactory = connectionFactory; + } + + public void setTableName(String tableName) { + this.tableName = tableName; + } + + protected Connection openDbConnection() { + return connectionFactory.openDbConnection(); + } + + protected void closeDbConnection(Connection connection) { + connectionFactory.closeDbConnection(connection); + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/Messages.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/Messages.java new file mode 100644 index 000000000..0399726b5 --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/Messages.java @@ -0,0 +1,47 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql; + +public enum Messages { + + ERR_NULL_LOCK_OWNER("Cannot acquire lock for resource [%s]: lock owner must be specified"), + ERR_LOCK_LOCKED_BY_OTHER("Cannot lock resource [%s] for [%s]: already locked by [%s]"), + ERR_UNLOCK_NOT_LOCKED("Error unlocking resource [%s]: resource is not locked"), + ERR_UNLOCK_LOCKED_BY_OTHER("Error unlocking resource [%s] by [%s]: resource is locked by [%s]"), + EXP_LOCK("Error locking resource [%s]."), + EXP_UNLOCK("Error unlocking resource [%s]."), + ; + + private String message; + + Messages(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } + + public String format(Object... s) { + return String.format(message, s); + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/MySqlConnectionFactory.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/MySqlConnectionFactory.java new file mode 100644 index 000000000..4ccd0c453 --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/MySqlConnectionFactory.java @@ -0,0 +1,34 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql; + +import java.sql.DriverManager; +import java.sql.SQLException; + +import org.openecomp.appc.dao.util.DefaultJdbcConnectionFactory; + +public class MySqlConnectionFactory extends DefaultJdbcConnectionFactory { + + protected void registedDriver() throws SQLException { + DriverManager.registerDriver(new com.mysql.jdbc.Driver()); + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/LockRecord.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/LockRecord.java new file mode 100644 index 000000000..d66804488 --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/LockRecord.java @@ -0,0 +1,71 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql.optimistic; + +class LockRecord { + + private String resource; + private String owner; + private long updated; + private long timeout; + private long ver; + + LockRecord(String resource) { + this.resource = resource; + } + + public String getResource() { + return resource; + } + + public String getOwner() { + return owner; + } + + public void setOwner(String owner) { + this.owner = owner; + } + + public long getUpdated() { + return updated; + } + + public void setUpdated(long updated) { + this.updated = updated; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public long getVer() { + return ver; + } + + public void setVer(long ver) { + this.ver = ver; + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/MySqlLockManager.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/MySqlLockManager.java new file mode 100644 index 000000000..e80133620 --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/MySqlLockManager.java @@ -0,0 +1,32 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql.optimistic; + +import java.sql.SQLException; + +public class MySqlLockManager extends SqlLockManager { + + @Override + protected boolean isDuplicatePkError(SQLException e) { + return (e.getErrorCode() == 1062); + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/SqlLockManager.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/SqlLockManager.java new file mode 100644 index 000000000..52c75d2b2 --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/SqlLockManager.java @@ -0,0 +1,263 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql.optimistic; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; + +import org.openecomp.appc.lockmanager.api.LockException; +import org.openecomp.appc.lockmanager.api.LockRuntimeException; +import org.openecomp.appc.lockmanager.impl.sql.JdbcLockManager; +import org.openecomp.appc.lockmanager.impl.sql.Messages; + +abstract class SqlLockManager extends JdbcLockManager { + + private static final String SQL_LOAD_LOCK_RECORD = "SELECT * FROM %s WHERE RESOURCE_ID=?"; + private static final String SQL_INSERT_LOCK_RECORD = "INSERT INTO %s (RESOURCE_ID, OWNER_ID, UPDATED, TIMEOUT, VER) VALUES (?, ?, ?, ?, ?)"; + private static final String SQL_UPDATE_LOCK_RECORD = "UPDATE %s SET OWNER_ID=?, UPDATED=?, TIMEOUT=?, VER=? WHERE RESOURCE_ID=? AND VER=?"; +// private static final String SQL_DELETE_LOCK_RECORD = "DELETE FROM %s WHERE RESOURCE_ID=? AND VER=?"; + private static final String SQL_CURRENT_TIMESTAMP = "SELECT CURRENT_TIMESTAMP()"; + + private String sqlLoadLockRecord; + private String sqlInsertLockRecord; + private String sqlUpdateLockRecord; +// private String sqlDeleteLockRecord; + + @Override + public boolean acquireLock(String resource, String owner) throws LockException { + return acquireLock(resource, owner, 0); + } + + @Override + public boolean acquireLock(String resource, String owner, long timeout) throws LockException { + if(owner == null) { + throw new LockRuntimeException(Messages.ERR_NULL_LOCK_OWNER.format(resource)); + } + boolean res = false; + Connection connection = openDbConnection(); + try { + res = lockResource(connection, resource, owner, timeout); + } finally { + closeDbConnection(connection); + } + return res; + } + + @Override + public void releaseLock(String resource, String owner) throws LockException { + Connection connection = openDbConnection(); + try { + unlockResource(connection, resource, owner); + } finally { + closeDbConnection(connection); + } + } + + @Override + public boolean isLocked(String resource) { + Connection connection=openDbConnection(); + try { + LockRecord lockRecord=loadLockRecord(connection,resource); + if(lockRecord==null){ + return false; + }else{ + if(lockRecord.getOwner()==null){ + return false; + }else if(isLockExpired(lockRecord, connection)){ + return false; + }else{ + return true; + } + } + } catch (SQLException e) { + throw new LockRuntimeException(Messages.EXP_LOCK.format(resource)); + }finally { + closeDbConnection(connection); + } + } + + private boolean lockResource(Connection connection, String resource, String owner, long timeout) throws LockException { + try { + boolean res = false; + LockRecord lockRecord = loadLockRecord(connection, resource); + if(lockRecord != null) { + // lock record already exists + String currentOwner = lockRecord.getOwner(); + if(currentOwner != null) { + if(isLockExpired(lockRecord, connection)) { + currentOwner = null; + } else if(!owner.equals(currentOwner)) { + throw new LockException(Messages.ERR_LOCK_LOCKED_BY_OTHER.format(resource, owner, currentOwner)); + } + } + // set new owner on the resource lock record + if(!updateLockRecord(connection, resource, owner, timeout, lockRecord.getVer())) { + // try again - maybe same owner updated the record + lockResource(connection, resource, owner, timeout); + } + if(currentOwner == null) { + // no one locked the resource before + res = true; + } + } else { + // resource record does not exist in lock table => create new record + try { + addLockRecord(connection, resource, owner, timeout); + res = true; + } catch(SQLException e) { + if(isDuplicatePkError(e)) { + // try again - maybe same owner inserted the record + lockResource(connection, resource, owner, timeout); + } else { + throw e; + } + } + } + return res; + } catch(SQLException e) { + throw new LockRuntimeException(Messages.EXP_LOCK.format(resource), e); + } + } + + protected boolean isDuplicatePkError(SQLException e) { + return e.getSQLState().startsWith("23"); + } + + private void unlockResource(Connection connection, String resource, String owner) throws LockException { + try { + LockRecord lockRecord = loadLockRecord(connection, resource); + if(lockRecord != null) { + // check if expired + if(isLockExpired(lockRecord, connection)) { + // lock is expired => no lock + lockRecord = null; + } + } + if((lockRecord == null) || (lockRecord.getOwner() == null)) { + // resource is not locked + throw new LockException(Messages.ERR_UNLOCK_NOT_LOCKED.format(resource)); + } + String currentOwner = lockRecord.getOwner(); + if(!owner.equals(currentOwner)) { + throw new LockException(Messages.ERR_UNLOCK_LOCKED_BY_OTHER.format(resource, owner, currentOwner)); + } + if (!updateLockRecord(connection, resource, null, 0, lockRecord.getVer())) { + unlockResource(connection, resource, owner); + } + // TODO delete record from table on lock release? +// deleteLockRecord(connection, resource, lockRecord.getVer()); + } catch(SQLException e) { + throw new LockRuntimeException(Messages.EXP_UNLOCK.format(resource), e); + } + } + + protected LockRecord loadLockRecord(Connection connection, String resource) throws SQLException { + LockRecord res = null; + if(sqlLoadLockRecord == null) { + sqlLoadLockRecord = String.format(SQL_LOAD_LOCK_RECORD, tableName); + } + try(PreparedStatement statement = connection.prepareStatement(sqlLoadLockRecord)) { + statement.setString(1, resource); + try(ResultSet resultSet = statement.executeQuery()) { + if(resultSet.next()) { + res = new LockRecord(resource); + res.setOwner(resultSet.getString(2)); + res.setUpdated(resultSet.getLong(3)); + res.setTimeout(resultSet.getLong(4)); + res.setVer(resultSet.getLong(5)); + } + } + } + return res; + } + + protected void addLockRecord(Connection connection, String resource, String owner, long timeout) throws SQLException { + if(sqlInsertLockRecord == null) { + sqlInsertLockRecord = String.format(SQL_INSERT_LOCK_RECORD, tableName); + } + try(PreparedStatement statement = connection.prepareStatement(sqlInsertLockRecord)) { + statement.setString(1, resource); + statement.setString(2, owner); + statement.setLong(3, getCurrentTime(connection)); + statement.setLong(4, timeout); + statement.setLong(5, 1); + statement.executeUpdate(); + } + } + + protected boolean updateLockRecord(Connection connection, String resource, String owner, long timeout, long ver) throws SQLException { + if(sqlUpdateLockRecord == null) { + sqlUpdateLockRecord = String.format(SQL_UPDATE_LOCK_RECORD, tableName); + } + try(PreparedStatement statement = connection.prepareStatement(sqlUpdateLockRecord)) { + long newVer = (ver >= Long.MAX_VALUE) ? 1 : (ver + 1); + statement.setString(1, owner); + statement.setLong(2, getCurrentTime(connection)); + statement.setLong(3, timeout); + statement.setLong(4, newVer); + statement.setString(5, resource); + statement.setLong(6, ver); + return (statement.executeUpdate() != 0); + } + } + +// protected void deleteLockRecord(Connection connection, String resource, long ver) throws SQLException { +// if(sqlDeleteLockRecord == null) { +// sqlDeleteLockRecord = String.format(SQL_DELETE_LOCK_RECORD, tableName); +// } +// try(PreparedStatement statement = connection.prepareStatement(sqlDeleteLockRecord)) { +// statement.setString(1, resource); +// statement.setLong(2, ver); +// statement.executeUpdate(); +// } +// } + + private boolean isLockExpired(LockRecord lockRecord, Connection connection) throws SQLException { + long timeout = lockRecord.getTimeout(); + if(timeout == 0) { + return false; + } + long updated = lockRecord.getUpdated(); + long now = getCurrentTime(connection); + long expiration = updated + timeout; + return (now > expiration); + } + + private long getCurrentTime(Connection connection) throws SQLException { + long res = -1; + if(connection != null) { + try(PreparedStatement statement = connection.prepareStatement(SQL_CURRENT_TIMESTAMP)) { + try(ResultSet resultSet = statement.executeQuery()) { + if(resultSet.next()) { + res = resultSet.getTimestamp(1).getTime(); + } + } + } + } + if(res == -1) { + res = System.currentTimeMillis(); + } + return res; + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/resources/OSGI-INF/blueprint/blueprint.xml b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/resources/OSGI-INF/blueprint/blueprint.xml new file mode 100644 index 000000000..6ec23eeb0 --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/main/resources/OSGI-INF/blueprint/blueprint.xml @@ -0,0 +1,41 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ============LICENSE_START======================================================= + openECOMP : APP-C + ================================================================================ + Copyright (C) 2017 AT&T Intellectual Property. All rights + reserved. + ================================================================================ + Licensed 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. + ============LICENSE_END========================================================= + --> + + +<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd"> + + <!--<bean id="lockManagerInMemoryBean" class="org.openecomp.appc.lockmanager.impl.inmemory.LockManagerInMemoryImpl" factory-method="getLockManager" scope="singleton"/>--> + <!--<service id="lockManagerInMemoryService" interface="org.openecomp.appc.lockmanager.api.LockManager" ref="lockManagerInMemoryBean"/>--> + + <service id="lockManagerMySqlOptimisticService" interface="org.openecomp.appc.lockmanager.api.LockManager"> + <bean class="org.openecomp.appc.lockmanager.impl.sql.optimistic.MySqlLockManager"> + <property name="connectionFactory"> + <bean class="org.openecomp.appc.dao.util.AppcJdbcConnectionFactory"> + <property name="schema" value="sdnctl"/> + </bean> + </property> + <property name="tableName" value="VNF_LOCK_MANAGEMENT"/> + </bean> + </service> +</blueprint> diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/api/LockManagerBaseTests.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/api/LockManagerBaseTests.java new file mode 100644 index 000000000..47e27f79d --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/api/LockManagerBaseTests.java @@ -0,0 +1,167 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.api; + +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.openecomp.appc.lockmanager.api.LockException; +import org.openecomp.appc.lockmanager.api.LockManager; + +public abstract class LockManagerBaseTests { + + protected enum Resource {Resource1, Resource2}; + protected enum Owner {A, B}; + + protected LockManager lockManager; + + @Before + public void beforeTest() { + lockManager = createLockManager(); + } + + protected abstract LockManager createLockManager(); + + @Test + public void testAcquireLock() throws LockException { + boolean lockRes = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name()); + try { + Assert.assertTrue(lockRes); + } finally { + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + } + } + + @Test + public void testAcquireLock_AlreadyLockedBySameOwner() throws LockException { + boolean lockRes1 = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name()); + try { + Assert.assertTrue(lockRes1); + boolean lockRes2 = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name()); + Assert.assertFalse(lockRes2); + } finally { + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + } + } + + @Test(expected = LockException.class) + public void testAcquireLock_AlreadyLockedByOtherOwner() throws LockException { + String owner2 = "B"; + boolean lockRes1 = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name()); + try { + Assert.assertTrue(lockRes1); + boolean lockRes2 = lockManager.acquireLock(Resource.Resource1.name(), owner2); + Assert.assertFalse(lockRes2); + } finally { + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + } + } + + @Test + public void testAcquireLock_LockDifferentResources() throws LockException { + boolean lockRes1 = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name()); + try { + Assert.assertTrue(lockRes1); + boolean lockRes2 = lockManager.acquireLock(Resource.Resource2.name(), Owner.B.name()); + try { + Assert.assertTrue(lockRes2); + } finally { + lockManager.releaseLock(Resource.Resource2.name(), Owner.B.name()); + } + } finally { + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + } + } + + @Test(expected = LockException.class) + public void testReleaseLock_NotLockedResource() throws LockException { + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + } + + @Test(expected = LockException.class) + public void testReleaseLock_LockedByOtherOwnerResource() throws LockException { + boolean lockRes1 = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name()); + try { + Assert.assertTrue(lockRes1); + lockManager.releaseLock(Resource.Resource1.name(), Owner.B.name()); + } finally { + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + } + } + + @Test(expected = LockException.class) + public void testAcquireLock_LockExpired() throws LockException, InterruptedException { + boolean lockRes1 = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name(), 50); + Assert.assertTrue(lockRes1); + Thread.sleep(1000); + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + } + + @Test + public void testAcquireLock_OtherLockExpired() throws LockException, InterruptedException { + boolean lockRes1 = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name(), 50); + Assert.assertTrue(lockRes1); + Thread.sleep(1000); + boolean lockRes2 = lockManager.acquireLock(Resource.Resource1.name(), Owner.B.name()); + try { + Assert.assertTrue(lockRes2); + }finally { + lockManager.releaseLock(Resource.Resource1.name(), Owner.B.name()); + } + } + + @Test + public void testIsLocked_WhenLocked() throws LockException, InterruptedException { + boolean lockRes1 = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name(), 50); + try { + Assert.assertTrue(lockManager.isLocked(Resource.Resource1.name())); + }finally { + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + } + } + + + @Test(expected = LockException.class) + public void testIsLocked_LockExpired() throws LockException, InterruptedException { + boolean lockRes1 = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name(), 50); + Assert.assertTrue(lockRes1); + Assert.assertTrue(lockManager.isLocked(Resource.Resource1.name())); + Thread.sleep(1000); + try { + Assert.assertFalse(lockManager.isLocked(Resource.Resource1.name())); + }finally { + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + } + } + + @Test + public void testIsLocked_LockReleased() throws LockException, InterruptedException { + boolean lockRes1 = lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name(), 50); + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + Assert.assertFalse(lockManager.isLocked(Resource.Resource1.name())); + } + + @Test + public void testIsLocked_NoLock() throws LockException, InterruptedException { + Assert.assertFalse(lockManager.isLocked(Resource.Resource1.name())); + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/inmemory/LockManagerInMemoryImplTest.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/inmemory/LockManagerInMemoryImplTest.java new file mode 100644 index 000000000..99faec23d --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/inmemory/LockManagerInMemoryImplTest.java @@ -0,0 +1,36 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.inmemory; + +import org.openecomp.appc.lockmanager.api.LockManager; +import org.openecomp.appc.lockmanager.api.LockManagerBaseTests; +import org.openecomp.appc.lockmanager.impl.inmemory.LockManagerInMemoryImpl; + + +public class LockManagerInMemoryImplTest extends LockManagerBaseTests { + + @Override + protected LockManager createLockManager() { + return LockManagerInMemoryImpl.getLockManager(); + } + +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/MySqlLockManagerBaseTests.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/MySqlLockManagerBaseTests.java new file mode 100644 index 000000000..da5607819 --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/MySqlLockManagerBaseTests.java @@ -0,0 +1,93 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql; + +import org.junit.Rule; +import org.junit.rules.TestName; +import org.openecomp.appc.dao.util.DefaultJdbcConnectionFactory; +import org.openecomp.appc.lockmanager.api.LockManager; +import org.openecomp.appc.lockmanager.api.LockManagerBaseTests; +import org.openecomp.appc.lockmanager.impl.sql.JdbcLockManager; +import org.openecomp.appc.lockmanager.impl.sql.MySqlConnectionFactory; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + +public abstract class MySqlLockManagerBaseTests extends LockManagerBaseTests { + + private static final boolean USE_REAL_DB = Boolean.getBoolean("lockmanager.tests.useRealDb"); + private static final String TABLE_LOCK_MANAGEMENT = "TEST_LOCK_MANAGEMENT"; + private static final String JDBC_URL = System.getProperty("lockmanager.tests.jdbcUrl", "jdbc:mysql://192.168.1.2/test"); + private static final String JDBC_USERNAME = System.getProperty("lockmanager.tests.jdbcUsername", "test"); + private static final String JDBC_PASSWORD = System.getProperty("lockmanager.tests.jdbcPassword", "123456"); + + protected static final int CONCURRENT_TEST_WAIT_TIME = 10; // secs + + @Rule + public TestName testName = new TestName(); + + @Override + protected LockManager createLockManager() { + JdbcLockManager jdbcLockManager = createJdbcLockManager(USE_REAL_DB); + DefaultJdbcConnectionFactory connectionFactory = new MySqlConnectionFactory(); + connectionFactory.setJdbcURL(JDBC_URL); + connectionFactory.setJdbcUserName(JDBC_USERNAME); + connectionFactory.setJdbcPassword(JDBC_PASSWORD); + jdbcLockManager.setConnectionFactory(connectionFactory); + jdbcLockManager.setTableName(TABLE_LOCK_MANAGEMENT); + System.out.println("=> Running LockManager test [" + jdbcLockManager.getClass().getName() + "." + testName.getMethodName() + "]" + (USE_REAL_DB ? ". JDBC URL is [" + JDBC_URL + "]" : "")); + clearTestLocks(jdbcLockManager); + return jdbcLockManager; + } + + protected abstract JdbcLockManager createJdbcLockManager(boolean useRealDb); + + protected boolean setSynchronizer(Synchronizer synchronizer) { + if(!(lockManager instanceof SynchronizerReceiver)) { + System.err.println("Skipping concurrency test [" + testName.getMethodName() + "] for LockManager of type " + lockManager.getClass()); + return false; + } + ((SynchronizerReceiver)lockManager).setSynchronizer(synchronizer); + return true; + } + + private static final String SQL_DELETE_LOCK_RECORD = String.format("DELETE FROM %s WHERE RESOURCE_ID=?", TABLE_LOCK_MANAGEMENT); + private void clearTestLocks(JdbcLockManager jdbcLockManager) { + Connection connection = jdbcLockManager.openDbConnection(); + if(connection == null) { + return; + } + try { + for(Resource resource: Resource.values()) { + try(PreparedStatement statement = connection.prepareStatement(SQL_DELETE_LOCK_RECORD)) { + statement.setString(1, resource.name()); + statement.executeUpdate(); + } + } + } catch(SQLException e) { + throw new RuntimeException("Cannot clear test resources in table", e); + } finally { + jdbcLockManager.closeDbConnection(connection); + } + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/Synchronizer.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/Synchronizer.java new file mode 100644 index 000000000..8d1f0bbfd --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/Synchronizer.java @@ -0,0 +1,80 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql; + +public class Synchronizer { + + private int participantNo; + private int participantCount; + + public Synchronizer(int participantNo) { + this.participantNo = participantNo; + } + + public int getParticipantCount() { + return participantCount; + } + + public void postLoadLockRecord(String resource, String owner) { + synchronized(this) { + waitForAllParticipants(this, participantNo, ++participantCount); + } + } + + public void preAddLockRecord(String resource, String owner) { + } + + public void postAddLockRecord(String resource, String owner) { + } + + public void preUpdateLockRecord(String resource, String owner) { + } + + public void postUpdateLockRecord(String resource, String owner) { + } + + public void releaseWait() { + synchronized(this) { + this.notifyAll(); + } + } + + protected void waitOn(Object obj, long timeout) { + try { + obj.wait(timeout); + } catch(InterruptedException e) { + throw new RuntimeException(e); + } + } + + protected void waitOn(Object obj) { + waitOn(obj, 0); + } + + protected void waitForAllParticipants(Object waitObj, int totalParticipantsNo, int currentParticipantsNo) { + if(totalParticipantsNo > currentParticipantsNo) { + waitOn(waitObj); + } else { + waitObj.notifyAll(); + } + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/SynchronizerReceiver.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/SynchronizerReceiver.java new file mode 100644 index 000000000..945fed10b --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/SynchronizerReceiver.java @@ -0,0 +1,27 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql; + +public interface SynchronizerReceiver { + + void setSynchronizer(Synchronizer synchronizer); +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/MySqlLockManagerMock.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/MySqlLockManagerMock.java new file mode 100644 index 000000000..3e6a236ba --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/MySqlLockManagerMock.java @@ -0,0 +1,132 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql.optimistic; + +import java.sql.Connection; +import java.sql.SQLException; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import org.openecomp.appc.lockmanager.impl.sql.Synchronizer; +import org.openecomp.appc.lockmanager.impl.sql.SynchronizerReceiver; +import org.openecomp.appc.lockmanager.impl.sql.optimistic.LockRecord; +import org.openecomp.appc.lockmanager.impl.sql.optimistic.MySqlLockManager; + +class MySqlLockManagerMock extends MySqlLockManager implements SynchronizerReceiver { + + private final ConcurrentMap<String, LockRecord> locks = new ConcurrentHashMap<>(); + private boolean useReal; + private Synchronizer synchronizer; + + MySqlLockManagerMock(boolean useReal) { + this.useReal = useReal; + } + + @Override + public void setSynchronizer(Synchronizer synchronizer) { + this.synchronizer = synchronizer; + } + + @Override + protected Connection openDbConnection() { + if(useReal) { + return super.openDbConnection(); + } + return null; + } + + @Override + protected void closeDbConnection(Connection connection) { + if(useReal) { + super.closeDbConnection(connection); + } + } + + @Override + protected LockRecord loadLockRecord(Connection connection, String resource) throws SQLException { + LockRecord res; + if(useReal) { + res = super.loadLockRecord(connection, resource); + } else { + res = locks.get(resource); + } + if(synchronizer != null) { + synchronizer.postLoadLockRecord(resource, (res == null) ? null : res.getOwner()); + } + return res; + } + + @Override + protected void addLockRecord(Connection connection, String resource, String owner, long timeout) throws SQLException { + if(synchronizer != null) { + synchronizer.preAddLockRecord(resource, owner); + } + try { + if(useReal) { + super.addLockRecord(connection, resource, owner, timeout); + return; + } + LockRecord lockRecord = new LockRecord(resource); + lockRecord.setOwner(owner); + lockRecord.setUpdated(System.currentTimeMillis()); + lockRecord.setTimeout(timeout); + lockRecord.setVer(1); + LockRecord prevLockRecord = locks.putIfAbsent(resource, lockRecord); + if(prevLockRecord != null) { + // simulate unique constraint violation + throw new SQLException("Duplicate PK exception", "23000", 1062); + } + } finally { + if(synchronizer != null) { + synchronizer.postAddLockRecord(resource, owner); + } + } + } + + @Override + protected boolean updateLockRecord(Connection connection, String resource, String owner, long timeout, long ver) throws SQLException { + if(synchronizer != null) { + synchronizer.preUpdateLockRecord(resource, owner); + } + try { + if(useReal) { + return super.updateLockRecord(connection, resource, owner, timeout, ver); + } + LockRecord lockRecord = loadLockRecord(connection, resource); + synchronized(lockRecord) { + // should be atomic operation + if(ver != lockRecord.getVer()) { + return false; + } + lockRecord.setOwner(owner); + lockRecord.setUpdated(System.currentTimeMillis()); + lockRecord.setTimeout(timeout); + lockRecord.setVer(ver + 1); + } + return true; + } finally { + if(synchronizer != null) { + synchronizer.postUpdateLockRecord(resource, owner); + } + } + } +} diff --git a/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/TestMySqlLockManager.java b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/TestMySqlLockManager.java new file mode 100644 index 000000000..6106dd84b --- /dev/null +++ b/appc-dispatcher/appc-dispatcher-common/lock-manager-lib/lock-manager-impl/src/test/java/org/openecomp/appc/lockmanager/impl/sql/optimistic/TestMySqlLockManager.java @@ -0,0 +1,227 @@ +/*- + * ============LICENSE_START======================================================= + * openECOMP : APP-C + * ================================================================================ + * Copyright (C) 2017 AT&T Intellectual Property. All rights + * reserved. + * ================================================================================ + * Licensed 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. + * ============LICENSE_END========================================================= + */ + +package org.openecomp.appc.lockmanager.impl.sql.optimistic; + +import org.junit.Assert; +import org.junit.Test; +import org.openecomp.appc.lockmanager.api.LockException; +import org.openecomp.appc.lockmanager.impl.sql.JdbcLockManager; +import org.openecomp.appc.lockmanager.impl.sql.MySqlLockManagerBaseTests; +import org.openecomp.appc.lockmanager.impl.sql.Synchronizer; + +import java.util.concurrent.*; + +public class TestMySqlLockManager extends MySqlLockManagerBaseTests { + + @Override + protected JdbcLockManager createJdbcLockManager(boolean useReal) { + return new MySqlLockManagerMock(useReal); + } + + @Test + public void testConcurrentLockDifferentOwners() throws LockException, InterruptedException, ExecutionException, TimeoutException { + + final int participantsNo = 2; + Synchronizer synchronizer = new Synchronizer(participantsNo) { + + private boolean wait = true; + + @Override + public void preAddLockRecord(String resource, String owner) { + if(Owner.A.name().equals(owner)) { + synchronized(this) { + if(wait) { + waitOn(this); + } + } + } + } + + @Override + public void postAddLockRecord(String resource, String owner) { + if(!Owner.A.name().equals(owner)) { + synchronized(this) { + notifyAll(); + wait = false; + } + } + } + + @Override + public void preUpdateLockRecord(String resource, String owner) { + preAddLockRecord(resource, owner); + } + + @Override + public void postUpdateLockRecord(String resource, String owner) { + postAddLockRecord(resource, owner); + } + }; + if(!setSynchronizer(synchronizer)) { + return; + } + ExecutorService executor = Executors.newFixedThreadPool(participantsNo); + // acquireLock by owner A should fail as it will wait for acquireLock by owner B + Future<Boolean> future1 = executor.submit(new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + try { + lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name()); + return false; + } catch(LockException e) { + // this call should fail as Synchronizer delays its lock to make sure the second call locks the resource first + Assert.assertEquals("Cannot lock resource [" + Resource.Resource1.name() + "] for [" + Owner.A.name() + "]: already locked by [" + Owner.B.name() + "]", e.getMessage()); + return true; + } + } + }); + try { + // acquireLock by owner B should success + Future<Boolean> future2 = executor.submit(new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + // this call should success as Synchronizer delays the above lock to make sure this call success to lock the resource + return lockManager.acquireLock(Resource.Resource1.name(), Owner.B.name()); + } + }); + try { + Assert.assertTrue(future2.get(CONCURRENT_TEST_WAIT_TIME, TimeUnit.SECONDS)); + Assert.assertTrue(future1.get(CONCURRENT_TEST_WAIT_TIME, TimeUnit.SECONDS)); + } finally { + future2.cancel(true); + } + } finally { + future1.cancel(true); + } + } + + @Test + public void testConcurrentLockSameOwner() throws LockException, InterruptedException, ExecutionException, TimeoutException { + final int participantsNo = 2; + Synchronizer synchronizer = new Synchronizer(participantsNo) { + + private boolean wait = true; + + @Override + public void preAddLockRecord(String resource, String owner) { + synchronized(this) { + if(wait) { + wait = false; + waitOn(this); + } + } + } + + @Override + public void postAddLockRecord(String resource, String owner) { + synchronized(this) { + notifyAll(); + } + } + }; + if(!setSynchronizer(synchronizer)) { + return; + } + ExecutorService executor = Executors.newFixedThreadPool(participantsNo); + // one acquireLock should return true and the other should return false + Callable<Boolean> callable = new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + return lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name()); + } + }; + Future<Boolean> future1 = executor.submit(callable); + try { + Future<Boolean> future2 = executor.submit(callable); + try { + boolean future1Res = future1.get(CONCURRENT_TEST_WAIT_TIME, TimeUnit.SECONDS); + boolean future2Res = future2.get(CONCURRENT_TEST_WAIT_TIME, TimeUnit.SECONDS); + // one of the lock requests should return true, the other one false as lock is requested simultaneously from 2 threads by same owner + Assert.assertNotEquals(future1Res, future2Res); + } finally { + future2.cancel(true); + } + } finally { + future1.cancel(true); + } + } + + @Test + public void testConcurrentUnlockSameOwner() throws LockException, InterruptedException, ExecutionException, TimeoutException { + lockManager.acquireLock(Resource.Resource1.name(), Owner.A.name()); + final int participantsNo = 2; + Synchronizer synchronizer = new Synchronizer(participantsNo) { + + private boolean wait = true; + + @Override + public void preUpdateLockRecord(String resource, String owner) { + synchronized(this) { + // make sure second call updates the LockRecord first + if(wait) { + wait = false; + waitOn(this); + } + } + } + + @Override + public void postUpdateLockRecord(String resource, String owner) { + synchronized(this) { + notifyAll(); + } + } + }; + if(!setSynchronizer(synchronizer)) { + return; + } + ExecutorService executor = Executors.newFixedThreadPool(participantsNo); + Callable<Boolean> callable = new Callable<Boolean>() { + @Override + public Boolean call() throws Exception { + try { + lockManager.releaseLock(Resource.Resource1.name(), Owner.A.name()); + // one of the unlock calls should success + return true; + } catch(LockException e) { + // one of the unlock calls should throw the LockException as the resource should already be unlocked by other call + Assert.assertEquals("Error unlocking resource [" + Resource.Resource1.name() + "]: resource is not locked", e.getMessage()); + return false; + } + } + }; + Future<Boolean> future1 = executor.submit(callable); + try { + Future<Boolean> future2 = executor.submit(callable); + try { + boolean future1Res = future1.get(CONCURRENT_TEST_WAIT_TIME, TimeUnit.SECONDS); + boolean future2Res = future2.get(CONCURRENT_TEST_WAIT_TIME, TimeUnit.SECONDS); + // one of the unlock calls should return true, the other one false as unlock is requested simultaneously from 2 threads by same owner + Assert.assertNotEquals(future1Res, future2Res); + } finally { + future2.cancel(true); + } + } finally { + future1.cancel(true); + } + } +} |