From dd7e7c73d8e0ab97ee94636efffe34b4ac6b6494 Mon Sep 17 00:00:00 2001 From: sarahcaseybot Date: Fri, 20 Mar 2020 14:11:00 -0400 Subject: [PATCH] Add lock dual read (#517) * Add lock dual read * small changes --- .../google/registry/model/server/Lock.java | 49 ++- .../registry/schema/server/LockDao.java | 61 +++- .../registry/schema/server/LockDaoTest.java | 88 ++++- package-lock.json | 326 +++++++++--------- 4 files changed, 337 insertions(+), 187 deletions(-) diff --git a/core/src/main/java/google/registry/model/server/Lock.java b/core/src/main/java/google/registry/model/server/Lock.java index ca5495441..ea1874792 100644 --- a/core/src/main/java/google/registry/model/server/Lock.java +++ b/core/src/main/java/google/registry/model/server/Lock.java @@ -78,6 +78,18 @@ public class Lock extends ImmutableObject implements Serializable { /** When the lock can be considered implicitly released. */ DateTime expirationTime; + public String getRequestLogId() { + return requestLogId; + } + + public DateTime getExpirationTime() { + return expirationTime; + } + + public DateTime getAcquiredTime() { + return acquiredTime; + } + /** When was the lock acquired. Used for logging. */ DateTime acquiredTime; @@ -89,10 +101,10 @@ public class Lock extends ImmutableObject implements Serializable { String tld; /** - * Create a new {@link Lock} for the given resource name in the specified tld (which can be - * null for cross-tld locks). + * Create a new {@link Lock} for the given resource name in the specified tld (which can be null + * for cross-tld locks). */ - private static Lock create( + public static Lock create( String resourceName, @Nullable String tld, String requestLogId, @@ -185,6 +197,18 @@ public class Lock extends ImmutableObject implements Serializable { // Checking if an unexpired lock still exists - if so, the lock can't be acquired. Lock lock = ofy().load().type(Lock.class).id(lockId).now(); + try { + jpaTm() + .transact( + () -> { + Optional cloudSqlLockOptional = + LockDao.load(resourceName, tld); + LockDao.compare(Optional.ofNullable(lock), cloudSqlLockOptional); + }); + } catch (Exception e) { + logger.atSevere().withCause(e).log( + "Issue loading and comparing lock from Cloud SQL"); + } if (lock != null) { logger.atInfo().log( "Loaded existing lock: %s for request: %s", lock.lockId, lock.requestLogId); @@ -221,12 +245,7 @@ public class Lock extends ImmutableObject implements Serializable { requestStatusChecker.getLogId(), now, leaseLength); - // cloudSqlLock should not already exist in Cloud SQL, but call delete - // just in case - // TODO: Remove this delete once dual read is added - LockDao.delete( - resourceName, Optional.ofNullable(tld).orElse("GLOBAL")); - LockDao.saveNew(cloudSqlLock); + LockDao.save(cloudSqlLock); }); } catch (Exception e) { logger.atSevere().withCause(e).log( @@ -251,6 +270,18 @@ public class Lock extends ImmutableObject implements Serializable { // this can happen if release() is called around the expiration time and the lock // expires underneath us. Lock loadedLock = ofy().load().type(Lock.class).id(lockId).now(); + try { + jpaTm() + .transact( + () -> { + Optional cloudSqlLockOptional = + LockDao.load(resourceName, tld); + LockDao.compare(Optional.ofNullable(loadedLock), cloudSqlLockOptional); + }); + } catch (Exception e) { + logger.atSevere().withCause(e).log( + "Issue loading and comparing lock from Cloud SQL"); + } if (Lock.this.equals(loadedLock)) { // Use noBackupOfy() so that we don't create a commit log entry for deleting the // lock. diff --git a/core/src/main/java/google/registry/schema/server/LockDao.java b/core/src/main/java/google/registry/schema/server/LockDao.java index 2f2a05ca2..93794afa5 100644 --- a/core/src/main/java/google/registry/schema/server/LockDao.java +++ b/core/src/main/java/google/registry/schema/server/LockDao.java @@ -18,18 +18,22 @@ import static google.registry.persistence.transaction.TransactionManagerFactory. import static google.registry.schema.server.Lock.GLOBAL; import static google.registry.util.PreconditionsUtils.checkArgumentNotNull; +import com.google.common.flogger.FluentLogger; import google.registry.schema.server.Lock.LockId; +import google.registry.util.DateTimeUtils; import java.util.Optional; /** Data access object class for {@link Lock}. */ public class LockDao { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + /** Saves the {@link Lock} object to Cloud SQL. */ - public static void saveNew(Lock lock) { + public static void save(Lock lock) { jpaTm() .transact( () -> { - jpaTm().getEntityManager().persist(lock); + jpaTm().getEntityManager().merge(lock); }); } @@ -76,4 +80,57 @@ public class LockDao { public static void delete(String resourceName) { delete(resourceName, GLOBAL); } + + /** + * Compares a {@link google.registry.model.server.Lock} object with a {@link Lock} object, logging + * a warning if there are any differences. + */ + public static void compare( + Optional datastoreLockOptional, + Optional cloudSqlLockOptional) { + if (!datastoreLockOptional.isPresent()) { + cloudSqlLockOptional.ifPresent( + value -> + logger.atWarning().log( + String.format( + "Cloud SQL lock for %s with tld %s should be null", + value.resourceName, value.tld))); + return; + } + google.registry.schema.server.Lock cloudSqlLock; + google.registry.model.server.Lock datastoreLock = datastoreLockOptional.get(); + if (cloudSqlLockOptional.isPresent()) { + cloudSqlLock = cloudSqlLockOptional.get(); + if (!datastoreLock.getRequestLogId().equals(cloudSqlLock.requestLogId)) { + logger.atWarning().log( + String.format( + "Datastore lock requestLogId of %s does not equal Cloud SQL lock requestLogId of" + + " %s", + datastoreLock.getRequestLogId(), cloudSqlLock.requestLogId)); + } + if (!datastoreLock + .getAcquiredTime() + .equals(DateTimeUtils.toJodaDateTime(cloudSqlLock.acquiredTime))) { + logger.atWarning().log( + String.format( + "Datastore lock acquiredTime of %s does not equal Cloud SQL lock acquiredTime of" + + " %s", + datastoreLock.getAcquiredTime(), + DateTimeUtils.toJodaDateTime(cloudSqlLock.acquiredTime))); + } + if (!datastoreLock + .getExpirationTime() + .equals(DateTimeUtils.toJodaDateTime(cloudSqlLock.expirationTime))) { + logger.atWarning().log( + String.format( + "Datastore lock expirationTime of %s does not equal Cloud SQL lock expirationTime" + + " of %s", + datastoreLock.getExpirationTime(), + DateTimeUtils.toJodaDateTime(cloudSqlLock.expirationTime))); + } + } else { + logger.atWarning().log( + String.format("Datastore lock: %s was not found in Cloud SQL", datastoreLock)); + } + } } diff --git a/core/src/test/java/google/registry/schema/server/LockDaoTest.java b/core/src/test/java/google/registry/schema/server/LockDaoTest.java index e54c9b1c1..853c857ab 100644 --- a/core/src/test/java/google/registry/schema/server/LockDaoTest.java +++ b/core/src/test/java/google/registry/schema/server/LockDaoTest.java @@ -15,13 +15,15 @@ package google.registry.schema.server; import static com.google.common.truth.Truth.assertThat; -import static org.junit.Assert.assertThrows; +import static google.registry.testing.LogsSubject.assertAboutLogs; +import com.google.common.testing.TestLogHandler; import google.registry.persistence.transaction.JpaTestRules; import google.registry.persistence.transaction.JpaTestRules.JpaIntegrationWithCoverageRule; import google.registry.testing.FakeClock; import java.util.Optional; -import javax.persistence.RollbackException; +import java.util.logging.Level; +import java.util.logging.Logger; import org.joda.time.Duration; import org.junit.Rule; import org.junit.Test; @@ -33,6 +35,8 @@ import org.junit.runners.JUnit4; public class LockDaoTest { private final FakeClock fakeClock = new FakeClock(); + private final TestLogHandler logHandler = new TestLogHandler(); + private final Logger loggerToIntercept = Logger.getLogger(LockDao.class.getCanonicalName()); @Rule public final JpaIntegrationWithCoverageRule jpaRule = @@ -42,29 +46,28 @@ public class LockDaoTest { public void save_worksSuccessfully() { Lock lock = Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2)); - LockDao.saveNew(lock); + LockDao.save(lock); Optional returnedLock = LockDao.load("testResource", "tld"); assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime); assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId); } @Test - public void save_failsWhenLockAlreadyExists() { + public void save_succeedsWhenLockAlreadyExists() { Lock lock = Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2)); - LockDao.saveNew(lock); + LockDao.save(lock); Lock lock2 = Lock.create("testResource", "tld", "testLogId2", fakeClock.nowUtc(), Duration.millis(4)); - RollbackException thrown = assertThrows(RollbackException.class, () -> LockDao.saveNew(lock2)); - assertThat(thrown.getCause().getCause().getCause().getMessage()) - .contains("duplicate key value violates unique constraint"); + LockDao.save(lock2); + assertThat(LockDao.load("testResource", "tld").get().requestLogId).isEqualTo("testLogId2"); } @Test public void save_worksSuccesfullyGlobalLock() { Lock lock = Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2)); - LockDao.saveNew(lock); + LockDao.save(lock); Optional returnedLock = LockDao.load("testResource"); assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime); assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId); @@ -74,7 +77,7 @@ public class LockDaoTest { public void load_worksSuccessfully() { Lock lock = Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2)); - LockDao.saveNew(lock); + LockDao.save(lock); Optional returnedLock = LockDao.load("testResource", "tld"); assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime); assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId); @@ -84,7 +87,7 @@ public class LockDaoTest { public void load_worksSuccessfullyGlobalLock() { Lock lock = Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2)); - LockDao.saveNew(lock); + LockDao.save(lock); Optional returnedLock = LockDao.load("testResource"); assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime); assertThat(returnedLock.get().requestLogId).isEqualTo(lock.requestLogId); @@ -100,7 +103,7 @@ public class LockDaoTest { public void delete_worksSuccesfully() { Lock lock = Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2)); - LockDao.saveNew(lock); + LockDao.save(lock); Optional returnedLock = LockDao.load("testResource", "tld"); assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime); LockDao.delete("testResource", "tld"); @@ -112,7 +115,7 @@ public class LockDaoTest { public void delete_worksSuccessfullyGlobalLock() { Lock lock = Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2)); - LockDao.saveNew(lock); + LockDao.save(lock); Optional returnedLock = LockDao.load("testResource"); assertThat(returnedLock.get().expirationTime).isEqualTo(lock.expirationTime); LockDao.delete("testResource"); @@ -124,4 +127,63 @@ public class LockDaoTest { public void delete_succeedsLockDoesntExist() { LockDao.delete("testResource"); } + + @Test + public void compare_logsWarningWhenCloudSqlLockMissing() { + loggerToIntercept.addHandler(logHandler); + google.registry.model.server.Lock datastoreLock = + google.registry.model.server.Lock.create( + "resourceName", "tld", "id", fakeClock.nowUtc(), Duration.millis(2)); + LockDao.compare(Optional.of(datastoreLock), Optional.empty()); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage( + Level.WARNING, + String.format("Datastore lock: %s was not found in Cloud SQL", datastoreLock)); + } + + @Test + public void compare_logsWarningWhenCloudSqlLockExistsWhenItShouldNot() { + loggerToIntercept.addHandler(logHandler); + Lock lock = + Lock.createGlobal("testResource", "testLogId", fakeClock.nowUtc(), Duration.millis(2)); + LockDao.compare(Optional.ofNullable(null), Optional.of(lock)); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage( + Level.WARNING, + String.format("Cloud SQL lock for testResource with tld GLOBAL should be null")); + } + + @Test + public void compare_logsWarningWhenLocksDontMatch() { + loggerToIntercept.addHandler(logHandler); + Lock cloudSqlLock = + Lock.create("testResource", "tld", "testLogId", fakeClock.nowUtc(), Duration.millis(2)); + google.registry.model.server.Lock datastoreLock = + google.registry.model.server.Lock.create( + "testResource", "tld", "wrong", fakeClock.nowUtc().minusDays(1), Duration.millis(3)); + LockDao.compare(Optional.of(datastoreLock), Optional.of(cloudSqlLock)); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage( + Level.WARNING, + String.format( + "Datastore lock requestLogId of wrong does not equal Cloud SQL lock requestLogId" + + " of testLogId")); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage( + Level.WARNING, + String.format( + "Datastore lock acquiredTime of 1969-12-31T00:00:00.000Z does not equal Cloud SQL" + + " lock acquiredTime of 1970-01-01T00:00:00.000Z")); + assertAboutLogs() + .that(logHandler) + .hasLogAtLevelWithMessage( + Level.WARNING, + String.format( + "Datastore lock expirationTime of 1969-12-31T00:00:00.003Z does not equal Cloud" + + " SQL lock expirationTime of 1970-01-01T00:00:00.002Z")); + } } diff --git a/package-lock.json b/package-lock.json index a193cfb98..21ab5dfaa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", "dev": true, "requires": { - "mime-types": "~2.1.24", + "mime-types": "2.1.24", "negotiator": "0.6.2" } }, @@ -26,7 +26,7 @@ "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, "requires": { - "es6-promisify": "^5.0.0" + "es6-promisify": "5.0.0" } }, "anymatch": { @@ -35,8 +35,8 @@ "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==", "dev": true, "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" + "normalize-path": "3.0.0", + "picomatch": "2.1.1" } }, "arraybuffer.slice": { @@ -51,7 +51,7 @@ "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "dev": true, "requires": { - "lodash": "^4.17.14" + "lodash": "4.17.15" } }, "async-limiter": { @@ -118,15 +118,15 @@ "dev": true, "requires": { "bytes": "3.1.0", - "content-type": "~1.0.4", + "content-type": "1.0.4", "debug": "2.6.9", - "depd": "~1.1.2", + "depd": "1.1.2", "http-errors": "1.7.2", "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", + "on-finished": "2.3.0", "qs": "6.7.0", "raw-body": "2.4.0", - "type-is": "~1.6.17" + "type-is": "1.6.18" } }, "brace-expansion": { @@ -135,7 +135,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -145,7 +145,7 @@ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "7.0.1" } }, "buffer-alloc": { @@ -154,8 +154,8 @@ "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "dev": true, "requires": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" + "buffer-alloc-unsafe": "1.1.0", + "buffer-fill": "1.0.0" } }, "buffer-alloc-unsafe": { @@ -194,14 +194,14 @@ "integrity": "sha512-dGmKLDdT3Gdl7fBUe8XK+gAtGmzy5Fn0XkkWQuYxGIgWVPPse2CxFA5mtrlD0TOHaHjEUqkWNyP1XdHoJES/4A==", "dev": true, "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.1.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.2.0" + "anymatch": "3.1.1", + "braces": "3.0.2", + "fsevents": "2.1.2", + "glob-parent": "5.1.0", + "is-binary-path": "2.1.0", + "is-glob": "4.0.1", + "normalize-path": "3.0.0", + "readdirp": "3.2.0" } }, "colors": { @@ -240,10 +240,10 @@ "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "buffer-from": "1.1.1", + "inherits": "2.0.3", + "readable-stream": "2.3.6", + "typedarray": "0.0.6" } }, "connect": { @@ -254,7 +254,7 @@ "requires": { "debug": "2.6.9", "finalhandler": "1.1.2", - "parseurl": "~1.3.3", + "parseurl": "1.3.3", "utils-merge": "1.0.1" } }, @@ -315,10 +315,10 @@ "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", "dev": true, "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "custom-event": "1.0.1", + "ent": "2.2.0", + "extend": "3.0.2", + "void-elements": "2.0.1" } }, "ee-first": { @@ -339,12 +339,12 @@ "integrity": "sha512-+VlKzHzMhaU+GsCIg4AoXF1UdDFjHHwMmMKqMJNDNLlUlejz58FCy4LBqB2YVJskHGYl06BatYWKP2TVdVXE5w==", "dev": true, "requires": { - "accepts": "~1.3.4", + "accepts": "1.3.7", "base64id": "1.0.0", "cookie": "0.3.1", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.0", - "ws": "~3.3.1" + "debug": "3.1.0", + "engine.io-parser": "2.1.3", + "ws": "3.3.3" }, "dependencies": { "debug": { @@ -366,14 +366,14 @@ "requires": { "component-emitter": "1.2.1", "component-inherit": "0.0.3", - "debug": "~3.1.0", - "engine.io-parser": "~2.1.1", + "debug": "3.1.0", + "engine.io-parser": "2.1.3", "has-cors": "1.1.0", "indexof": "0.0.1", "parseqs": "0.0.5", "parseuri": "0.0.5", - "ws": "~3.3.1", - "xmlhttprequest-ssl": "~1.5.4", + "ws": "3.3.3", + "xmlhttprequest-ssl": "1.5.5", "yeast": "0.1.2" }, "dependencies": { @@ -395,10 +395,10 @@ "dev": true, "requires": { "after": "0.8.2", - "arraybuffer.slice": "~0.0.7", + "arraybuffer.slice": "0.0.7", "base64-arraybuffer": "0.1.5", "blob": "0.0.5", - "has-binary2": "~1.0.2" + "has-binary2": "1.0.3" } }, "ent": { @@ -419,7 +419,7 @@ "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", "dev": true, "requires": { - "es6-promise": "^4.0.3" + "es6-promise": "4.2.8" } }, "escape-html": { @@ -458,7 +458,7 @@ "integrity": "sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU=", "dev": true, "requires": { - "pend": "~1.2.0" + "pend": "1.2.0" } }, "fill-range": { @@ -467,7 +467,7 @@ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", "dev": true, "requires": { - "to-regex-range": "^5.0.1" + "to-regex-range": "5.0.1" } }, "finalhandler": { @@ -477,12 +477,12 @@ "dev": true, "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "encodeurl": "1.0.2", + "escape-html": "1.0.3", + "on-finished": "2.3.0", + "parseurl": "1.3.3", + "statuses": "1.5.0", + "unpipe": "1.0.0" } }, "flatted": { @@ -497,7 +497,7 @@ "integrity": "sha512-CRcPzsSIbXyVDl0QI01muNDu69S8trU4jArW9LpOt2WtC6LyUJetcIrmfHsRBx7/Jb6GHJUiuqyYxPooFfNt6A==", "dev": true, "requires": { - "debug": "^3.0.0" + "debug": "3.2.6" }, "dependencies": { "debug": { @@ -506,7 +506,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -523,7 +523,7 @@ "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", "dev": true, "requires": { - "null-check": "^1.0.0" + "null-check": "1.0.0" } }, "fs-extra": { @@ -532,9 +532,9 @@ "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, "requires": { - "graceful-fs": "^4.1.2", - "jsonfile": "^4.0.0", - "universalify": "^0.1.0" + "graceful-fs": "4.2.3", + "jsonfile": "4.0.0", + "universalify": "0.1.2" } }, "fs.realpath": { @@ -556,12 +556,12 @@ "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-parent": { @@ -570,7 +570,7 @@ "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==", "dev": true, "requires": { - "is-glob": "^4.0.1" + "is-glob": "4.0.1" } }, "google-closure-library": { @@ -605,10 +605,10 @@ "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", "dev": true, "requires": { - "depd": "~1.1.2", + "depd": "1.1.2", "inherits": "2.0.3", "setprototypeof": "1.1.1", - "statuses": ">= 1.5.0 < 2", + "statuses": "1.5.0", "toidentifier": "1.0.0" } }, @@ -618,9 +618,9 @@ "integrity": "sha512-84I2iJM/n1d4Hdgc6y2+qY5mDaz2PUVjlg9znE9byl+q0uC3DeByqBGReQu5tpLK0TAqTIXScRUV+dg7+bUPpQ==", "dev": true, "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "eventemitter3": "4.0.0", + "follow-redirects": "1.9.0", + "requires-port": "1.0.0" } }, "https-proxy-agent": { @@ -629,8 +629,8 @@ "integrity": "sha512-+ML2Rbh6DAuee7d07tYGEKOEi2voWPUGan+ExdPbPW6Z3svq+JCqr0v8WmKPOkz1vOVykPCBSuobe7G8GJUtVg==", "dev": true, "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" + "agent-base": "4.3.0", + "debug": "3.2.6" }, "dependencies": { "debug": { @@ -639,7 +639,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -656,7 +656,7 @@ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } }, "indexof": { @@ -671,8 +671,8 @@ "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "dev": true, "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" } }, "inherits": { @@ -687,7 +687,7 @@ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "requires": { - "binary-extensions": "^2.0.0" + "binary-extensions": "2.0.0" } }, "is-extglob": { @@ -702,7 +702,7 @@ "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", "dev": true, "requires": { - "is-extglob": "^2.1.1" + "is-extglob": "2.1.1" } }, "is-number": { @@ -723,7 +723,7 @@ "integrity": "sha512-8cJBL5tTd2OS0dM4jz07wQd5g0dCCqIhUxPIGtZfa5L6hWlvV5MHTITy/DBAsF+Oe2LS1X3krBUhNwaGUWpWxw==", "dev": true, "requires": { - "buffer-alloc": "^1.2.0" + "buffer-alloc": "1.2.0" } }, "isexe": { @@ -744,7 +744,7 @@ "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "dev": true, "requires": { - "graceful-fs": "^4.1.6" + "graceful-fs": "4.2.3" } }, "karma": { @@ -753,30 +753,30 @@ "integrity": "sha512-L5SIaXEYqzrh6b1wqYC42tNsFMx2PWuxky84pK9coK09MvmL7mxii3G3bZBh/0rvD27lqDd0le9jyhzvwif73A==", "dev": true, "requires": { - "bluebird": "^3.3.0", - "body-parser": "^1.16.1", - "braces": "^3.0.2", - "chokidar": "^3.0.0", - "colors": "^1.1.0", - "connect": "^3.6.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.0", - "flatted": "^2.0.0", - "glob": "^7.1.1", - "graceful-fs": "^4.1.2", - "http-proxy": "^1.13.0", - "isbinaryfile": "^3.0.0", - "lodash": "^4.17.14", - "log4js": "^4.0.0", - "mime": "^2.3.1", - "minimatch": "^3.0.2", - "optimist": "^0.6.1", - "qjobs": "^1.1.4", - "range-parser": "^1.2.0", - "rimraf": "^2.6.0", - "safe-buffer": "^5.0.1", + "bluebird": "3.7.1", + "body-parser": "1.19.0", + "braces": "3.0.2", + "chokidar": "3.3.0", + "colors": "1.4.0", + "connect": "3.7.0", + "di": "0.0.1", + "dom-serialize": "2.2.1", + "flatted": "2.0.1", + "glob": "7.1.6", + "graceful-fs": "4.2.3", + "http-proxy": "1.18.0", + "isbinaryfile": "3.0.3", + "lodash": "4.17.15", + "log4js": "4.5.1", + "mime": "2.4.4", + "minimatch": "3.0.4", + "optimist": "0.6.1", + "qjobs": "1.2.0", + "range-parser": "1.2.1", + "rimraf": "2.7.1", + "safe-buffer": "5.2.0", "socket.io": "2.1.1", - "source-map": "^0.6.1", + "source-map": "0.6.1", "tmp": "0.0.33", "useragent": "2.3.0" } @@ -787,8 +787,8 @@ "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", "dev": true, "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" + "fs-access": "1.0.1", + "which": "1.3.1" } }, "karma-closure": { @@ -803,7 +803,7 @@ "integrity": "sha512-iuC0hmr9b+SNn1DaUD2QEYtUxkS1J+bSJSn7ejdEexs7P8EYvA1CWkEdrDQ+8jVH3AgWlCNwjYsT1chjcNW9lA==", "dev": true, "requires": { - "jasmine-core": "^3.3" + "jasmine-core": "3.4.0" } }, "lodash": { @@ -818,11 +818,11 @@ "integrity": "sha512-EEEgFcE9bLgaYUKuozyFfytQM2wDHtXn4tAN41pkaxpNjAykv11GVdeI4tHtmPWW4Xrgh9R/2d7XYghDVjbKKw==", "dev": true, "requires": { - "date-format": "^2.0.0", - "debug": "^4.1.1", - "flatted": "^2.0.0", - "rfdc": "^1.1.4", - "streamroller": "^1.0.6" + "date-format": "2.1.0", + "debug": "4.1.1", + "flatted": "2.0.1", + "rfdc": "1.1.4", + "streamroller": "1.0.6" }, "dependencies": { "debug": { @@ -831,7 +831,7 @@ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -848,8 +848,8 @@ "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } }, "media-typer": { @@ -885,7 +885,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -956,7 +956,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "optimist": { @@ -965,8 +965,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "minimist": "0.0.10", + "wordwrap": "0.0.3" } }, "os-tmpdir": { @@ -981,7 +981,7 @@ "integrity": "sha1-1SCKNzjkZ2bikbouoXNoSSGouJ0=", "dev": true, "requires": { - "better-assert": "~1.0.0" + "better-assert": "1.0.2" } }, "parseuri": { @@ -990,7 +990,7 @@ "integrity": "sha1-gCBKUNTbt3m/3G6+J3jZDkvOMgo=", "dev": true, "requires": { - "better-assert": "~1.0.0" + "better-assert": "1.0.2" } }, "parseurl": { @@ -1047,14 +1047,14 @@ "integrity": "sha512-t3MmTWzQxPRP71teU6l0jX47PHXlc4Z52sQv4LJQSZLq1ttkKS2yGM3gaI57uQwZkNaoGd0+HPPMELZkcyhlqA==", "dev": true, "requires": { - "debug": "^4.1.0", - "extract-zip": "^1.6.6", - "https-proxy-agent": "^3.0.0", - "mime": "^2.0.3", - "progress": "^2.0.1", - "proxy-from-env": "^1.0.0", - "rimraf": "^2.6.1", - "ws": "^6.1.0" + "debug": "4.1.1", + "extract-zip": "1.6.7", + "https-proxy-agent": "3.0.1", + "mime": "2.4.4", + "progress": "2.0.3", + "proxy-from-env": "1.0.0", + "rimraf": "2.7.1", + "ws": "6.2.1" }, "dependencies": { "debug": { @@ -1063,7 +1063,7 @@ "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -1078,7 +1078,7 @@ "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", "dev": true, "requires": { - "async-limiter": "~1.0.0" + "async-limiter": "1.0.1" } } } @@ -1119,13 +1119,13 @@ "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.1", + "safe-buffer": "5.1.2", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" }, "dependencies": { "isarray": { @@ -1148,7 +1148,7 @@ "integrity": "sha512-crk4Qu3pmXwgxdSgGhgA/eXiJAPQiX4GMOZZMXnqKxHX7TaoL+3gQVo/WeuAiogr07DpnfjIMpXXa+PAIvwPGQ==", "dev": true, "requires": { - "picomatch": "^2.0.4" + "picomatch": "2.1.1" } }, "requires-port": { @@ -1169,7 +1169,7 @@ "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "dev": true, "requires": { - "glob": "^7.1.3" + "glob": "7.1.6" } }, "safe-buffer": { @@ -1196,12 +1196,12 @@ "integrity": "sha512-rORqq9c+7W0DAK3cleWNSyfv/qKXV99hV4tZe+gGLfBECw3XEhBy7x85F3wypA9688LKjtwO9pX9L33/xQI8yA==", "dev": true, "requires": { - "debug": "~3.1.0", - "engine.io": "~3.2.0", - "has-binary2": "~1.0.2", - "socket.io-adapter": "~1.1.0", + "debug": "3.1.0", + "engine.io": "3.2.1", + "has-binary2": "1.0.3", + "socket.io-adapter": "1.1.1", "socket.io-client": "2.1.1", - "socket.io-parser": "~3.2.0" + "socket.io-parser": "3.2.0" }, "dependencies": { "debug": { @@ -1231,15 +1231,15 @@ "base64-arraybuffer": "0.1.5", "component-bind": "1.0.0", "component-emitter": "1.2.1", - "debug": "~3.1.0", - "engine.io-client": "~3.2.0", - "has-binary2": "~1.0.2", + "debug": "3.1.0", + "engine.io-client": "3.2.1", + "has-binary2": "1.0.3", "has-cors": "1.1.0", "indexof": "0.0.1", "object-component": "0.0.3", "parseqs": "0.0.5", "parseuri": "0.0.5", - "socket.io-parser": "~3.2.0", + "socket.io-parser": "3.2.0", "to-array": "0.1.4" }, "dependencies": { @@ -1261,7 +1261,7 @@ "dev": true, "requires": { "component-emitter": "1.2.1", - "debug": "~3.1.0", + "debug": "3.1.0", "isarray": "2.0.1" }, "dependencies": { @@ -1294,11 +1294,11 @@ "integrity": "sha512-3QC47Mhv3/aZNFpDDVO44qQb9gwB9QggMEE0sQmkTAwBVYdBRWISdsywlkfm5II1Q5y/pmrHflti/IgmIzdDBg==", "dev": true, "requires": { - "async": "^2.6.2", - "date-format": "^2.0.0", - "debug": "^3.2.6", - "fs-extra": "^7.0.1", - "lodash": "^4.17.14" + "async": "2.6.3", + "date-format": "2.1.0", + "debug": "3.2.6", + "fs-extra": "7.0.1", + "lodash": "4.17.15" }, "dependencies": { "debug": { @@ -1307,7 +1307,7 @@ "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", "dev": true, "requires": { - "ms": "^2.1.1" + "ms": "2.1.2" } }, "ms": { @@ -1324,7 +1324,7 @@ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.2" }, "dependencies": { "safe-buffer": { @@ -1341,7 +1341,7 @@ "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "dev": true, "requires": { - "os-tmpdir": "~1.0.2" + "os-tmpdir": "1.0.2" } }, "to-array": { @@ -1356,7 +1356,7 @@ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, "requires": { - "is-number": "^7.0.0" + "is-number": "7.0.0" } }, "toidentifier": { @@ -1372,7 +1372,7 @@ "dev": true, "requires": { "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "mime-types": "2.1.24" } }, "typedarray": { @@ -1405,8 +1405,8 @@ "integrity": "sha512-4AoH4pxuSvHCjqLO04sU6U/uE65BYza8l/KKBS0b0hnUPWi+cQ2BpeTEwejCSx9SPV5/U03nniDTrWx5NrmKdw==", "dev": true, "requires": { - "lru-cache": "4.1.x", - "tmp": "0.0.x" + "lru-cache": "4.1.5", + "tmp": "0.0.33" } }, "util-deprecate": { @@ -1433,7 +1433,7 @@ "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "isexe": "^2.0.0" + "isexe": "2.0.0" } }, "wordwrap": { @@ -1454,9 +1454,9 @@ "integrity": "sha512-nnWLa/NwZSt4KQJu51MYlCcSQ5g7INpOrOMt4XV8j4dqTXdmlUmSHQ8/oLC069ckre0fRsgfvsKwbTdtKLCDkA==", "dev": true, "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" + "async-limiter": "1.0.1", + "safe-buffer": "5.1.2", + "ultron": "1.1.1" }, "dependencies": { "safe-buffer": { @@ -1485,7 +1485,7 @@ "integrity": "sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU=", "dev": true, "requires": { - "fd-slicer": "~1.0.1" + "fd-slicer": "1.0.1" } }, "yeast": {