[lxc-devel] [pylxd/master] Bug/280/long running container

ajkavanagh on Github lxc-bot at linuxcontainers.org
Mon Mar 19 17:40:48 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 962 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180319/bfffbe87/attachment.bin>
-------------- next part --------------
From 5487c9015c89a57ed275c9d7b9f524ff78d2fc36 Mon Sep 17 00:00:00 2001
From: Alex Kavanagh <alex at ajkavanagh.co.uk>
Date: Sun, 18 Mar 2018 08:44:07 +0000
Subject: [PATCH 1/3] Track end of command exec from lxd properly

The bug presented as the operation not still existing at the lxd
daemon when the end of the command was reached.  After much testing,
it seemed that the pylxd client was closing the websockets when the
empty message was received on them, which turned out to be too late
for some kinds of output.  Instead, tracking when the operation
completed, and then waiting for the sockets to close meant that the
return code of the command could be obtained, and the wait for the
sockets to be closed by the lxd daemon ensured completion.  The code
also waits for the ws4py manager to clean up and waits on the threads to
end.  This helps with multiple commands.

Signed-off-by: Alex Kavanagh <alex at ajkavanagh.co.uk>
---
 pylxd/models/container.py | 15 +++++++++++----
 1 file changed, 11 insertions(+), 4 deletions(-)

diff --git a/pylxd/models/container.py b/pylxd/models/container.py
index bc7f984..8f41507 100644
--- a/pylxd/models/container.py
+++ b/pylxd/models/container.py
@@ -293,10 +293,20 @@ def execute(self, commands, environment={}):
 
             manager.start()
 
+            # watch for the end of the command:
+            while True:
+                operation = self.client.operations.get(operation_id)
+                if 'return' in operation.metadata:
+                    break
+                time.sleep(.5)
+
             while len(manager.websockets.values()) > 0:
                 time.sleep(.1)
 
-            operation = self.client.operations.get(operation_id)
+            stdout.close()
+            stderr.close()
+            manager.stop()
+            manager.join()
 
             return _ContainerExecuteResult(
                 operation.metadata['return'], stdout.data, stderr.data)
@@ -382,9 +392,6 @@ def handshake_ok(self):
         self.buffer = []
 
     def received_message(self, message):
-        if len(message.data) == 0:
-            self.close()
-            self.manager.remove(self)
         if message.encoding:
             self.buffer.append(message.data.decode(message.encoding))
         else:

From 7b25b73e506117d6fb217a97f8bb5d5360cb6d2c Mon Sep 17 00:00:00 2001
From: Alex Kavanagh <alex at ajkavanagh.co.uk>
Date: Mon, 19 Mar 2018 17:36:18 +0000
Subject: [PATCH 2/3] Add testing files that demonstrate the issue

These files demonstrated the bug/280 issue and that the associated
fix works.  However, their nature makes them tricky to incorporate into
a gating integration test due to networking requirements in the
containers.

Signed-off-by: Alex Kavanagh <alex at ajkavanagh.co.uk>
---
 contrib_testing/README.md          |  5 ++++
 contrib_testing/local-http-test.py | 51 +++++++++++++++++++++++++++++++++++++
 contrib_testing/local-unix-test.py | 51 +++++++++++++++++++++++++++++++++++++
 contrib_testing/remote-test.py     | 52 ++++++++++++++++++++++++++++++++++++++
 4 files changed, 159 insertions(+)
 create mode 100644 contrib_testing/README.md
 create mode 100755 contrib_testing/local-http-test.py
 create mode 100755 contrib_testing/local-unix-test.py
 create mode 100755 contrib_testing/remote-test.py

diff --git a/contrib_testing/README.md b/contrib_testing/README.md
new file mode 100644
index 0000000..8c8e5a8
--- /dev/null
+++ b/contrib_testing/README.md
@@ -0,0 +1,5 @@
+# Contrib Testing
+
+The files in this directory help to test some obscure parts of the execute
+command on containers.  They should be integrated into the test suite, but it's
+tricky to do that programmatically.
diff --git a/contrib_testing/local-http-test.py b/contrib_testing/local-http-test.py
new file mode 100755
index 0000000..3e47bca
--- /dev/null
+++ b/contrib_testing/local-http-test.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+
+import datetime
+import pylxd
+import requests
+import time
+
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
+
+
+requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+
+
+def log(s):
+    now = datetime.datetime.utcnow()
+    print("{} - {}".format(now, s))
+
+
+def create_and_update(client):
+    log("Creating...")
+    base = client.containers.create({
+        'name': 'ubuntu-1604',
+        'source': {
+            'type': 'image',
+            'protocol': 'simplestreams',
+            'server': 'https://images.linuxcontainers.org',
+            'alias': 'ubuntu/xenial/amd64'
+        }
+    }, wait=True)
+    log("starting...")
+    base.start(wait=True)
+    while len(base.state().network['eth0']['addresses']) < 2:
+        time.sleep(1)
+    commands = [
+        ['apt-get', 'update'],
+        ['apt-get', 'install', 'openssh-server', 'sudo', 'man', '-y']
+    ]
+    for command in commands:
+        log("command: {}".format(command))
+        result = base.execute(command)
+        log("result: {}".format(result.exit_code))
+        log("stdout: {}".format(result.stdout))
+        log("stderr: {}".format(result.stderr))
+
+
+if __name__ == '__main__':
+    client = pylxd.Client("https://127.0.0.1:8443/", verify=False)
+    log("Authenticating...")
+    client.authenticate('password')
+
+    create_and_update(client)
diff --git a/contrib_testing/local-unix-test.py b/contrib_testing/local-unix-test.py
new file mode 100755
index 0000000..580c0d5
--- /dev/null
+++ b/contrib_testing/local-unix-test.py
@@ -0,0 +1,51 @@
+#!/usr/bin/env python3
+
+import datetime
+import pylxd
+import requests
+import time
+
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
+
+
+requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+
+
+def log(s):
+    now = datetime.datetime.utcnow()
+    print("{} - {}".format(now, s))
+
+
+def create_and_update(client):
+    log("Creating...")
+    base = client.containers.create({
+        'name': 'ubuntu-1604',
+        'source': {
+            'type': 'image',
+            'protocol': 'simplestreams',
+            'server': 'https://images.linuxcontainers.org',
+            'alias': 'ubuntu/xenial/amd64'
+        }
+    }, wait=True)
+    log("starting...")
+    base.start(wait=True)
+    while len(base.state().network['eth0']['addresses']) < 2:
+        time.sleep(1)
+    commands = [
+        ['apt-get', 'update'],
+        ['apt-get', 'install', 'openssh-server', 'sudo', 'man', '-y']
+    ]
+    for command in commands:
+        log("command: {}".format(command))
+        result = base.execute(command)
+        log("result: {}".format(result.exit_code))
+        log("stdout: {}".format(result.stdout))
+        log("stderr: {}".format(result.stderr))
+
+
+if __name__ == '__main__':
+    client = pylxd.Client()
+    log("Authenticating...")
+    client.authenticate('password')
+
+    create_and_update(client)
diff --git a/contrib_testing/remote-test.py b/contrib_testing/remote-test.py
new file mode 100755
index 0000000..46b373c
--- /dev/null
+++ b/contrib_testing/remote-test.py
@@ -0,0 +1,52 @@
+#!/usr/bin/env python3
+
+import datetime
+import pylxd
+import requests
+import time
+
+from requests.packages.urllib3.exceptions import InsecureRequestWarning
+
+
+requests.packages.urllib3.disable_warnings(InsecureRequestWarning)
+
+
+def log(s):
+    now = datetime.datetime.utcnow()
+    print("{} - {}".format(now, s))
+
+
+def create_and_update(client):
+    log("Creating...")
+    base = client.containers.create({
+        'name': 'ubuntu-1604',
+        'source': {
+            'type': 'image',
+            'protocol': 'simplestreams',
+            'server': 'https://images.linuxcontainers.org',
+            'alias': 'ubuntu/xenial/amd64'
+        }
+    }, wait=True)
+    log("starting...")
+    base.start(wait=True)
+    while len(base.state().network['eth0']['addresses']) < 2:
+        time.sleep(1)
+    commands = [
+        ['sleep', '10'],
+        ['apt-get', 'update'],
+        ['apt-get', 'install', 'openssh-server', 'sudo', 'man', '-y']
+    ]
+    for command in commands:
+        log("command: {}".format(command))
+        result = base.execute(command)
+        log("result: {}".format(result.exit_code))
+        log("stdout: {}".format(result.stdout))
+        log("stderr: {}".format(result.stderr))
+
+
+if __name__ == '__main__':
+    client = pylxd.Client("https://10.245.162.33:8443/", verify=False)
+    log("Authenticating...")
+    client.authenticate('password')
+
+    create_and_update(client)

From 4f9ab13f6d43083ad31df5b1046f89e885bba172 Mon Sep 17 00:00:00 2001
From: Alex Kavanagh <alex at ajkavanagh.co.uk>
Date: Mon, 19 Mar 2018 17:38:56 +0000
Subject: [PATCH 3/3] Add ignore for contrib_testing from the module build

Signed-off-by: Alex Kavanagh <alex at ajkavanagh.co.uk>
---
 MANIFEST.in | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/MANIFEST.in b/MANIFEST.in
index 90f8a7a..09defac 100644
--- a/MANIFEST.in
+++ b/MANIFEST.in
@@ -2,5 +2,6 @@ include AUTHORS
 include ChangeLog
 exclude .gitignore
 exclude .gitreview
+exclude contrib_testing
 
-global-exclude *.pyc
\ No newline at end of file
+global-exclude *.pyc


More information about the lxc-devel mailing list