summaryrefslogtreecommitdiffstats
path: root/oti/event-handler/otihandler/docker_client.py
diff options
context:
space:
mode:
Diffstat (limited to 'oti/event-handler/otihandler/docker_client.py')
-rw-r--r--oti/event-handler/otihandler/docker_client.py175
1 files changed, 175 insertions, 0 deletions
diff --git a/oti/event-handler/otihandler/docker_client.py b/oti/event-handler/otihandler/docker_client.py
new file mode 100644
index 0000000..621a1ec
--- /dev/null
+++ b/oti/event-handler/otihandler/docker_client.py
@@ -0,0 +1,175 @@
+# ================================================================================
+# Copyright (c) 2019-2020 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=========================================================
+
+"""client interface to docker"""
+
+import docker
+import json
+import logging
+import time
+
+from otihandler.config import Config
+from otihandler.consul_client import ConsulClient
+from otihandler.utils import decrypt
+
+
+# class DockerClientError(RuntimeError):
+# pass
+
+class DockerClientConnectionError(RuntimeError):
+ pass
+
+
+class DockerClient(object):
+ """
+ All Docker logins are in Consul's key-value store under
+ "docker_plugin/docker_logins" as a list of json objects where
+ each object is a single login:
+
+ [{ "username": "XXXX", "password": "yyyy",
+ "registry": "hostname.domain:18443" }]
+ """
+
+ _logger = logging.getLogger("oti_handler.docker_client")
+
+ def __init__(self, docker_host, reauth=False):
+ """Create Docker client
+
+ Args:
+ -----
+ reauth: (boolean) Forces reauthentication, e.g., Docker login
+ """
+
+ (fqdn, port) = ConsulClient.get_service_fqdn_port(docker_host, node_meta=True)
+ base_url = "https://{}:{}".format(fqdn, port)
+
+ try:
+ tls_config = docker.tls.TLSConfig(
+ client_cert=(
+ Config.tls_server_ca_chain_file,
+ Config.tls_private_key_file
+ )
+ )
+ self._client = docker.APIClient(base_url=base_url, tls=tls_config, version='auto', timeout=60)
+
+ for dcl in ConsulClient.get_value("docker_plugin/docker_logins"):
+ dcl['password'] = decrypt(dcl['password'])
+ dcl["reauth"] = reauth
+ self._client.login(**dcl)
+
+ # except requests.exceptions.RequestException as e:
+ except Exception as e:
+ msg = "DockerClient.__init__({}) attempt to {} with TLS got exception {}: {!s}".format(
+ docker_host, base_url, type(e).__name__, e)
+
+ # Then try connecting to dockerhost without TLS
+ try:
+ base_url = "tcp://{}:{}".format(fqdn, port)
+ self._client = docker.APIClient(base_url=base_url, tls=False, version='auto', timeout=60)
+
+ for dcl in ConsulClient.get_value("docker_plugin/docker_logins"):
+ dcl['password'] = decrypt(dcl['password'])
+ dcl["reauth"] = reauth
+ self._client.login(**dcl)
+
+ # except requests.exceptions.RequestException as e:
+ except Exception as e:
+ msg = "{}\nDockerClient.__init__({}) attempt to {} without TLS got exception {}: {!s}".format(
+ msg, docker_host, base_url, type(e).__name__, e)
+ DockerClient._logger.error(msg)
+ raise DockerClientConnectionError(msg)
+
+ @staticmethod
+ def build_cmd(script_path, use_sh=True, msg_type="dti", **kwargs):
+ """Build command to execute"""
+
+ data = json.dumps(kwargs or {})
+
+ if use_sh:
+ return ['/bin/sh', script_path, msg_type, data]
+ else:
+ return [script_path, msg_type, data]
+
+ def notify_for_reconfiguration(self, container_id, cmd):
+ """Notify Docker container that reconfiguration occurred
+
+ Notify the Docker container by doing Docker exec of passed-in command
+
+ Args:
+ -----
+ container_id: (string)
+ cmd: (list) of strings each entry being part of the command
+ """
+
+ for attempts_remaining in range(11,-1,-1):
+ try:
+ result = self._client.exec_create(container=container_id, cmd=cmd)
+ except docker.errors.APIError as e:
+ # e # 500 Server Error: Internal Server Error ("{"message":"Container 624108d1ab96f24b568662ca0e5ffc39b59c1c57431aec0bef231fb62b04e166 is not running"}")
+ DockerClient._logger.debug("exec_create() returned APIError: {!s}".format(e))
+
+ # e.message # 500 Server Error: Internal Server Error
+ # DockerClient._logger.debug("e.message: {}".format(e.message))
+ # e.response.status_code # 500
+ # DockerClient._logger.debug("e.response.status_code: {}".format(e.response.status_code))
+ # e.response.reason # Internal Server Error
+ # DockerClient._logger.debug("e.response.reason: {}".format(e.response.reason))
+ # e.explanation # {"message":"Container 624108d1ab96f24b568662ca0e5ffc39b59c1c57431aec0bef231fb62b04e166 is not running"}
+ # DockerClient._logger.debug("e.explanation: {}".format(e.explanation))
+
+ # docker container restarting can wait
+ if e.explanation and 'is restarting' in e.explanation.lower():
+ DockerClient._logger.debug("notification exec_create() experienced: {!s}".format(e))
+ if attempts_remaining == 0:
+ result = None
+ break
+ time.sleep(10)
+ # elif e.explanation and 'no such container' in e.explanation.lower():
+ # elif e.explanation and 'is not running' in e.explanation.lower():
+ else:
+ DockerClient._logger.warn("aborting notification exec_create() because exception {}: {!s}".format(type(e).__name__, e))
+ return str(e) # don't raise or CM will retry usually forever
+ # raise DockerClientError(e)
+ except Exception as e:
+ DockerClient._logger.warn("aborting notification exec_create() because exception {}: {!s}".format(
+ type(e).__name__, e))
+ return str(e) # don't raise or CM will retry usually forever
+ # raise DockerClientError(e)
+ else:
+ break
+ if not result:
+ DockerClient._logger.warn("aborting notification exec_create() because docker exec failed")
+ return "notification unsuccessful" # failed to get an exec_id, perhaps trying multiple times, so don't raise or CM will retry usually forever
+ DockerClient._logger.debug("notification exec_create() succeeded")
+
+ for attempts_remaining in range(11,-1,-1):
+ try:
+ result = self._client.exec_start(exec_id=result['Id'])
+ except Exception as e:
+ DockerClient._logger.debug("notification exec_start() got exception {}: {!s}".format(type(e).__name__, e))
+ if attempts_remaining == 0:
+ DockerClient._logger.warn("aborting notification exec_start() because exception {}: {!s}".format(type(e).__name__, e))
+ return str(e) # don't raise or CM will retry usually forever
+ # raise DockerClientError(e)
+ time.sleep(10)
+ else:
+ break
+ DockerClient._logger.debug("notification exec_start() succeeded")
+
+ DockerClient._logger.info("Pass to docker exec {} {} {}".format(
+ container_id, cmd, result))
+
+ return result