You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by sj...@apache.org on 2014/12/19 13:28:05 UTC

[1/5] incubator-brooklyn git commit: Tests for catalog items versioning functionality.

Repository: incubator-brooklyn
Updated Branches:
  refs/heads/master d26463b65 -> 72cb9724c


Tests for catalog items versioning functionality.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/5ad1fdd7
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/5ad1fdd7
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/5ad1fdd7

Branch: refs/heads/master
Commit: 5ad1fdd7c7eff37098446c0a8808a9847652d7fb
Parents: 67d05d6
Author: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Authored: Mon Dec 15 18:07:28 2014 +0200
Committer: Svetoslav Neykov <sv...@cloudsoftcorp.com>
Committed: Mon Dec 15 18:07:28 2014 +0200

----------------------------------------------------------------------
 .../catalog/internal/CatalogVersioningTest.java | 133 ++++++++++++
 .../catalog/CatalogYamlVersioningTest.java      | 203 +++++++++++++++++++
 2 files changed, 336 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ad1fdd7/core/src/test/java/brooklyn/catalog/internal/CatalogVersioningTest.java
----------------------------------------------------------------------
diff --git a/core/src/test/java/brooklyn/catalog/internal/CatalogVersioningTest.java b/core/src/test/java/brooklyn/catalog/internal/CatalogVersioningTest.java
new file mode 100644
index 0000000..179ce8f
--- /dev/null
+++ b/core/src/test/java/brooklyn/catalog/internal/CatalogVersioningTest.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package brooklyn.catalog.internal;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.catalog.BrooklynCatalog;
+import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogPredicates;
+import brooklyn.entity.basic.Entities;
+import brooklyn.management.internal.LocalManagementContext;
+import brooklyn.test.entity.LocalManagementContextForTests;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+public class CatalogVersioningTest {
+    private LocalManagementContext managementContext;
+    private BrooklynCatalog catalog;
+    
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() throws Exception {
+        managementContext = LocalManagementContextForTests.newInstance();
+        catalog = managementContext.getCatalog();
+    }
+    
+    @AfterMethod(alwaysRun = true)
+    public void tearDown() throws Exception {
+        if (managementContext != null) Entities.destroyAll(managementContext);
+    }
+
+    @Test
+    public void testAddVersioned() {
+        String symbolicName = "sampleId";
+        String version = "0.1.0";
+        createCatalogItem(symbolicName, version);
+        assertSingleCatalogItem(symbolicName, version);
+    }
+
+    @Test
+    public void testAddSameVersionFails() {
+        String symbolicName = "sampleId";
+        String version = "0.1.0";
+        createCatalogItem(symbolicName, version);
+        createCatalogItem(symbolicName, version);
+        //forced update assumed in the above call
+        assertSingleCatalogItem(symbolicName, version);
+    }
+    
+    @Test
+    public void testGetLatest() {
+        String symbolicName = "sampleId";
+        String v1 = "0.1.0";
+        String v2 = "0.2.0";
+        createCatalogItem(symbolicName, v1);
+        createCatalogItem(symbolicName, v2);
+        CatalogItem<?, ?> item = catalog.getCatalogItem(symbolicName, BasicBrooklynCatalog.DEFAULT_VERSION);
+        assertEquals(item.getSymbolicName(), symbolicName);
+        assertEquals(item.getVersion(), v2);
+    }
+    
+    @Test
+    public void testGetLatestStable() {
+        String symbolicName = "sampleId";
+        String v1 = "0.1.0";
+        String v2 = "0.2.0-SNAPSHOT";
+        createCatalogItem(symbolicName, v1);
+        createCatalogItem(symbolicName, v2);
+        CatalogItem<?, ?> item = catalog.getCatalogItem(symbolicName, BasicBrooklynCatalog.DEFAULT_VERSION);
+        assertEquals(item.getSymbolicName(), symbolicName);
+        assertEquals(item.getVersion(), v1);
+    }
+    
+    @Test
+    public void testDelete() {
+        String symbolicName = "sampleId";
+        String version = "0.1.0";
+        createCatalogItem(symbolicName, version);
+        assertSingleCatalogItem(symbolicName, version);
+        assertTrue(catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName))).iterator().hasNext());
+        catalog.deleteCatalogItem(symbolicName, version);
+        assertFalse(catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName))).iterator().hasNext());
+    }
+
+    @Test
+    public void testList() {
+        String symbolicName = "sampleId";
+        String v1 = "0.1.0";
+        String v2 = "0.2.0-SNAPSHOT";
+        createCatalogItem(symbolicName, v1);
+        createCatalogItem(symbolicName, v2);
+        Iterable<CatalogItem<Object, Object>> items = catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName)));
+        assertEquals(Iterables.size(items), 2);
+    }
+    
+    @SuppressWarnings("deprecation")
+    private void createCatalogItem(String symbolicName, String version) {
+        catalog.addItem(CatalogItemBuilder.newEntity(symbolicName, version).
+                plan("services:\n- type: brooklyn.entity.basic.BasicEntity")
+                .build());
+    }
+
+    private void assertSingleCatalogItem(String symbolicName, String version) {
+        Iterable<CatalogItem<Object, Object>> items = catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName)));
+        CatalogItem<Object, Object> item = Iterables.getOnlyElement(items);
+        assertEquals(item.getSymbolicName(), symbolicName);
+        assertEquals(item.getVersion(), version);
+    }
+
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/5ad1fdd7/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
----------------------------------------------------------------------
diff --git a/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
new file mode 100644
index 0000000..5b43c99
--- /dev/null
+++ b/usage/camp/src/test/java/io/brooklyn/camp/brooklyn/catalog/CatalogYamlVersioningTest.java
@@ -0,0 +1,203 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package io.brooklyn.camp.brooklyn.catalog;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+import static org.testng.Assert.fail;
+import io.brooklyn.camp.brooklyn.AbstractYamlTest;
+
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+import brooklyn.catalog.BrooklynCatalog;
+import brooklyn.catalog.CatalogItem;
+import brooklyn.catalog.CatalogPredicates;
+import brooklyn.catalog.internal.BasicBrooklynCatalog;
+import brooklyn.entity.Entity;
+
+import com.google.common.base.Predicates;
+import com.google.common.collect.Iterables;
+
+public class CatalogYamlVersioningTest extends AbstractYamlTest {
+    
+    private BrooklynCatalog catalog;
+    
+    @BeforeMethod(alwaysRun = true)
+    public void setUp() {
+        super.setUp();
+        catalog = mgmt().getCatalog();
+    }
+
+    @Test
+    public void testAddItem() {
+        String symbolicName = "sampleId";
+        String version = "0.1.0";
+        addCatalogEntity(symbolicName, version);
+        assertSingleCatalogItem(symbolicName, version);
+    }
+
+    @Test
+    public void testAddUnversionedItem() {
+        String symbolicName = "sampleId";
+        addCatalogEntity(symbolicName, null);
+        assertSingleCatalogItem(symbolicName, BasicBrooklynCatalog.NO_VERSION);
+    }
+
+    @Test
+    public void testAddSameVersionFails() {
+        String symbolicName = "sampleId";
+        String version = "0.1.0";
+        addCatalogEntity(symbolicName, version);
+        try {
+            addCatalogEntity(symbolicName, version);
+            fail("Expected to fail");
+        } catch (IllegalStateException e) {
+            assertEquals(e.getMessage(), "Updating existing catalog entries is forbidden: " + symbolicName + ":" + version + ". Use forceUpdate argument to override.");
+        }
+    }
+    
+    @Test
+    public void testAddSameVersionForce() {
+        String symbolicName = "sampleId";
+        String version = "0.1.0";
+        addCatalogEntity(symbolicName, version);
+        forceCatalogUpdate();
+        String expectedType = "brooklyn.entity.basic.BasicApplication";
+        addCatalogEntity(symbolicName, version, expectedType);
+        CatalogItem<?, ?> item = catalog.getCatalogItem(symbolicName, version);
+        assertTrue(item.getPlanYaml().contains(expectedType), "Version not updated");
+    }
+    
+    @Test
+    public void testGetLatest() {
+        String symbolicName = "sampleId";
+        String v1 = "0.1.0";
+        String v2 = "0.2.0";
+        addCatalogEntity(symbolicName, v1);
+        addCatalogEntity(symbolicName, v2);
+        CatalogItem<?, ?> item = catalog.getCatalogItem(symbolicName, BasicBrooklynCatalog.DEFAULT_VERSION);
+        assertEquals(item.getVersion(), v2);
+    }
+    
+    @Test
+    public void testGetLatestStable() {
+        String symbolicName = "sampleId";
+        String v1 = "0.1.0";
+        String v2 = "0.2.0-SNAPSHOT";
+        addCatalogEntity(symbolicName, v1);
+        addCatalogEntity(symbolicName, v2);
+        CatalogItem<?, ?> item = catalog.getCatalogItem(symbolicName, BasicBrooklynCatalog.DEFAULT_VERSION);
+        assertEquals(item.getVersion(), v1);
+    }
+
+    @Test
+    public void testDelete() {
+        String symbolicName = "sampleId";
+        String version = "0.1.0";
+        addCatalogEntity(symbolicName, version);
+        assertTrue(catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName))).iterator().hasNext());
+        catalog.deleteCatalogItem(symbolicName, version);
+        assertFalse(catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName))).iterator().hasNext());
+    }
+    
+    @Test
+    public void testDeleteDefault() {
+        String symbolicName = "sampleId";
+        addCatalogEntity(symbolicName, null);
+        assertTrue(catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName))).iterator().hasNext());
+        catalog.deleteCatalogItem(symbolicName, BasicBrooklynCatalog.NO_VERSION);
+        assertFalse(catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName))).iterator().hasNext());
+    }
+    
+    @Test
+    public void testList() {
+        String symbolicName = "sampleId";
+        String v1 = "0.1.0";
+        String v2 = "0.2.0-SNAPSHOT";
+        addCatalogEntity(symbolicName, v1);
+        addCatalogEntity(symbolicName, v2);
+        Iterable<CatalogItem<Object, Object>> items = catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName)));
+        assertEquals(Iterables.size(items), 2);
+    }
+    
+    @Test
+    public void testVersionedReference() throws Exception {
+        String symbolicName = "sampleId";
+        String parentName = "parentId";
+        String v1 = "0.1.0";
+        String v2 = "0.2.0";
+        String expectedType = "brooklyn.entity.basic.BasicApplication";
+
+        addCatalogEntity(symbolicName, v1, expectedType);
+        addCatalogEntity(symbolicName, v2);
+        addCatalogEntity(parentName, v1, symbolicName + ":" + v1);
+
+        Entity app = createAndStartApplication(
+                "services:",
+                "- type: " + parentName + ":" + v1);
+
+        assertEquals(app.getEntityType().getName(), expectedType);
+    }
+
+    @Test
+    public void testUnversionedReference() throws Exception {
+        String symbolicName = "sampleId";
+        String parentName = "parentId";
+        String v1 = "0.1.0";
+        String v2 = "0.2.0";
+        String expectedType = "brooklyn.entity.basic.BasicApplication";
+
+        addCatalogEntity(symbolicName, v1);
+        addCatalogEntity(symbolicName, v2, expectedType);
+        addCatalogEntity(parentName, v1, symbolicName);
+
+        Entity app = createAndStartApplication(
+                "services:",
+                "- type: " + parentName + ":" + v1);
+
+        assertEquals(app.getEntityType().getName(), expectedType);
+    }
+
+    private void assertSingleCatalogItem(String symbolicName, String version) {
+        Iterable<CatalogItem<Object, Object>> items = catalog.getCatalogItems(CatalogPredicates.symbolicName(Predicates.equalTo(symbolicName)));
+        CatalogItem<Object, Object> item = Iterables.getOnlyElement(items);
+        assertEquals(item.getSymbolicName(), symbolicName);
+        assertEquals(item.getVersion(), version);
+    }
+    
+    private void addCatalogEntity(String symbolicName, String version) {
+        addCatalogEntity(symbolicName, version, "brooklyn.entity.basic.BasicEntity");
+    }
+
+    private void addCatalogEntity(String symbolicName, String version, String type) {
+        addCatalogItem(
+            "brooklyn.catalog:",
+            "  id: " + symbolicName,
+            "  name: My Catalog App",
+            "  description: My description",
+            "  icon_url: classpath://path/to/myicon.jpg",
+            (version != null ? "  version: " + version : ""),
+            "",
+            "services:",
+            "- type: " + type);
+    }
+
+}


[4/5] incubator-brooklyn git commit: This closes #407

Posted by sj...@apache.org.
This closes #407


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/3200c0cf
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/3200c0cf
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/3200c0cf

Branch: refs/heads/master
Commit: 3200c0cf2d11dad490661d620f5254c699ef7d40
Parents: d26463b 25aaff9
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Fri Dec 19 12:20:16 2014 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Fri Dec 19 12:20:16 2014 +0000

----------------------------------------------------------------------
 .../main/java/brooklyn/util/net/Networking.java | 20 +++++++++++++++++---
 1 file changed, 17 insertions(+), 3 deletions(-)
----------------------------------------------------------------------



[5/5] incubator-brooklyn git commit: This closes #396

Posted by sj...@apache.org.
This closes #396


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/72cb9724
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/72cb9724
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/72cb9724

Branch: refs/heads/master
Commit: 72cb9724cfc95c7573ec6eb2b65776c9d80fc873
Parents: 3200c0c 5ad1fdd
Author: Sam Corbett <sa...@cloudsoftcorp.com>
Authored: Fri Dec 19 12:21:54 2014 +0000
Committer: Sam Corbett <sa...@cloudsoftcorp.com>
Committed: Fri Dec 19 12:21:54 2014 +0000

----------------------------------------------------------------------
 .../catalog/internal/CatalogVersioningTest.java | 133 ++++++++++++
 .../catalog/CatalogYamlVersioningTest.java      | 203 +++++++++++++++++++
 2 files changed, 336 insertions(+)
----------------------------------------------------------------------



[2/5] incubator-brooklyn git commit: Fix illegal argument in isPortAvailable

Posted by sj...@apache.org.
Fix illegal argument in isPortAvailable

JRE docs say that the wildcard address can only be used on bind()
operations, but there's a case where we might use it in a connect()
call - this is undefined behaviour. Correct the code to pass in the
loopback address to connect() instead of the wildcard address.


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/25aaff93
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/25aaff93
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/25aaff93

Branch: refs/heads/master
Commit: 25aaff937a31f73a94f6caadb2fd6da303670f8b
Parents: 22e1c68
Author: Richard Downer <ri...@apache.org>
Authored: Thu Dec 18 14:02:57 2014 +0000
Committer: Richard Downer <ri...@apache.org>
Committed: Fri Dec 19 09:33:24 2014 +0000

----------------------------------------------------------------------
 .../src/main/java/brooklyn/util/net/Networking.java     | 12 ++++++++++--
 1 file changed, 10 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/25aaff93/utils/common/src/main/java/brooklyn/util/net/Networking.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/net/Networking.java b/utils/common/src/main/java/brooklyn/util/net/Networking.java
index 98fd828..77edb6f 100644
--- a/utils/common/src/main/java/brooklyn/util/net/Networking.java
+++ b/utils/common/src/main/java/brooklyn/util/net/Networking.java
@@ -78,12 +78,19 @@ public class Networking {
         if (port < MIN_PORT_NUMBER || port > MAX_PORT_NUMBER) {
             throw new IllegalArgumentException("Invalid start port: " + port);
         }
+
+        // For some operations it's not valid to pass ANY_NIC (0.0.0.0).
+        // We substitute for the loopback address in those cases.
+        InetAddress localAddressNotAny = (localAddress==null || ANY_NIC.equals(localAddress))
+                ? LOOPBACK
+                : ANY_NIC;
+
         Stopwatch watch = Stopwatch.createStarted();
         try {
             try {
                 Socket s = new Socket();
                 s.setSoTimeout(250);
-                s.connect(new InetSocketAddress(localAddress, port), 250);
+                s.connect(new InetSocketAddress(localAddressNotAny, port), 250);
                 try {
                     s.close();
                 } catch (Exception e) {}
@@ -123,7 +130,8 @@ public class Networking {
             
             
             if (localAddress==null || ANY_NIC.equals(localAddress)) {
-                // sometimes 0.0.0.0 can be bound to even if 127.0.0.1 has the port as in use; check 127.0.0.1 if 0.0.0.0 was requested
+                // sometimes 0.0.0.0 can be bound to even if 127.0.0.1 has the port as in use;
+                // check all interfaces if 0.0.0.0 was requested
                 Enumeration<NetworkInterface> nis = null;
                 try {
                     nis = NetworkInterface.getNetworkInterfaces();


[3/5] incubator-brooklyn git commit: nextAvailablePort, give better error messages

Posted by sj...@apache.org.
nextAvailablePort, give better error messages

Validate that the given argument is in the valid range for port numbers.
If not possible to find a port in the valid range, throw a descriptive
exception message (instead of trying an invalid port number and getting
an IllegalArgumentException from the JRE)


Project: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/commit/22e1c68d
Tree: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/tree/22e1c68d
Diff: http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/diff/22e1c68d

Branch: refs/heads/master
Commit: 22e1c68d8e0561e6e9152e1ab0c3c2083af5b32e
Parents: c4d907a
Author: Richard Downer <ri...@apache.org>
Authored: Thu Dec 18 14:01:06 2014 +0000
Committer: Richard Downer <ri...@apache.org>
Committed: Fri Dec 19 09:33:24 2014 +0000

----------------------------------------------------------------------
 utils/common/src/main/java/brooklyn/util/net/Networking.java | 8 +++++++-
 1 file changed, 7 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/22e1c68d/utils/common/src/main/java/brooklyn/util/net/Networking.java
----------------------------------------------------------------------
diff --git a/utils/common/src/main/java/brooklyn/util/net/Networking.java b/utils/common/src/main/java/brooklyn/util/net/Networking.java
index d338080..98fd828 100644
--- a/utils/common/src/main/java/brooklyn/util/net/Networking.java
+++ b/utils/common/src/main/java/brooklyn/util/net/Networking.java
@@ -48,6 +48,8 @@ import com.google.common.base.Throwables;
 import com.google.common.net.HostAndPort;
 import com.google.common.primitives.UnsignedBytes;
 
+import static com.google.common.base.Preconditions.checkArgument;
+
 public class Networking {
 
     private static final Logger log = LoggerFactory.getLogger(Networking.class);
@@ -148,7 +150,11 @@ public class Networking {
     }
     /** returns the first port available on the local machine >= the port supplied */
     public static int nextAvailablePort(int port) {
-        while (!isPortAvailable(port)) port++;
+        checkArgument(port >= MIN_PORT_NUMBER && port <= MAX_PORT_NUMBER, "requested port %s is outside the valid range of %s to %s", port, MIN_PORT_NUMBER, MAX_PORT_NUMBER);
+        int originalPort = port;
+        while (!isPortAvailable(port) && port <= MAX_PORT_NUMBER) port++;
+        if (port > MAX_PORT_NUMBER)
+            throw new RuntimeException("unable to find a free port at or above " + originalPort);
         return port;
     }