1 # ================================================================================
2 # Copyright (c) 2019-2020 AT&T Intellectual Property. All rights reserved.
3 # ================================================================================
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at
8 # http://www.apache.org/licenses/LICENSE-2.0
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 # ============LICENSE_END=========================================================
17 """client interface to docker"""
24 from otihandler.config import Config
25 from otihandler.consul_client import ConsulClient
26 from otihandler.utils import decrypt
29 # class DockerClientError(RuntimeError):
32 class DockerClientConnectionError(RuntimeError):
36 class DockerClient(object):
38 All Docker logins are in Consul's key-value store under
39 "docker_plugin/docker_logins" as a list of json objects where
40 each object is a single login:
42 [{ "username": "XXXX", "password": "yyyy",
43 "registry": "hostname.domain:18443" }]
46 _logger = logging.getLogger("oti_handler.docker_client")
48 def __init__(self, docker_host, reauth=False):
49 """Create Docker client
53 reauth: (boolean) Forces reauthentication, e.g., Docker login
56 (fqdn, port) = ConsulClient.get_service_fqdn_port(docker_host, node_meta=True)
57 base_url = "https://{}:{}".format(fqdn, port)
60 tls_config = docker.tls.TLSConfig(
62 Config.tls_server_ca_chain_file,
63 Config.tls_private_key_file
66 self._client = docker.APIClient(base_url=base_url, tls=tls_config, version='auto', timeout=60)
68 for dcl in ConsulClient.get_value("docker_plugin/docker_logins"):
69 dcl['password'] = decrypt(dcl['password'])
70 dcl["reauth"] = reauth
71 self._client.login(**dcl)
73 # except requests.exceptions.RequestException as e:
74 except Exception as e:
75 msg = "DockerClient.__init__({}) attempt to {} with TLS got exception {}: {!s}".format(
76 docker_host, base_url, type(e).__name__, e)
78 # Then try connecting to dockerhost without TLS
80 base_url = "tcp://{}:{}".format(fqdn, port)
81 self._client = docker.APIClient(base_url=base_url, tls=False, version='auto', timeout=60)
83 for dcl in ConsulClient.get_value("docker_plugin/docker_logins"):
84 dcl['password'] = decrypt(dcl['password'])
85 dcl["reauth"] = reauth
86 self._client.login(**dcl)
88 # except requests.exceptions.RequestException as e:
89 except Exception as e:
90 msg = "{}\nDockerClient.__init__({}) attempt to {} without TLS got exception {}: {!s}".format(
91 msg, docker_host, base_url, type(e).__name__, e)
92 DockerClient._logger.error(msg)
93 raise DockerClientConnectionError(msg)
96 def build_cmd(script_path, use_sh=True, msg_type="dti", **kwargs):
97 """Build command to execute"""
99 data = json.dumps(kwargs or {})
102 return ['/bin/sh', script_path, msg_type, data]
104 return [script_path, msg_type, data]
106 def notify_for_reconfiguration(self, container_id, cmd):
107 """Notify Docker container that reconfiguration occurred
109 Notify the Docker container by doing Docker exec of passed-in command
113 container_id: (string)
114 cmd: (list) of strings each entry being part of the command
117 for attempts_remaining in range(11,-1,-1):
119 result = self._client.exec_create(container=container_id, cmd=cmd)
120 except docker.errors.APIError as e:
121 # e # 500 Server Error: Internal Server Error ("{"message":"Container 624108d1ab96f24b568662ca0e5ffc39b59c1c57431aec0bef231fb62b04e166 is not running"}")
122 DockerClient._logger.debug("exec_create() returned APIError: {!s}".format(e))
124 # e.message # 500 Server Error: Internal Server Error
125 # DockerClient._logger.debug("e.message: {}".format(e.message))
126 # e.response.status_code # 500
127 # DockerClient._logger.debug("e.response.status_code: {}".format(e.response.status_code))
128 # e.response.reason # Internal Server Error
129 # DockerClient._logger.debug("e.response.reason: {}".format(e.response.reason))
130 # e.explanation # {"message":"Container 624108d1ab96f24b568662ca0e5ffc39b59c1c57431aec0bef231fb62b04e166 is not running"}
131 # DockerClient._logger.debug("e.explanation: {}".format(e.explanation))
133 # docker container restarting can wait
134 if e.explanation and 'is restarting' in e.explanation.lower():
135 DockerClient._logger.debug("notification exec_create() experienced: {!s}".format(e))
136 if attempts_remaining == 0:
140 # elif e.explanation and 'no such container' in e.explanation.lower():
141 # elif e.explanation and 'is not running' in e.explanation.lower():
143 DockerClient._logger.warn("aborting notification exec_create() because exception {}: {!s}".format(type(e).__name__, e))
144 return str(e) # don't raise or CM will retry usually forever
145 # raise DockerClientError(e)
146 except Exception as e:
147 DockerClient._logger.warn("aborting notification exec_create() because exception {}: {!s}".format(
148 type(e).__name__, e))
149 return str(e) # don't raise or CM will retry usually forever
150 # raise DockerClientError(e)
154 DockerClient._logger.warn("aborting notification exec_create() because docker exec failed")
155 return "notification unsuccessful" # failed to get an exec_id, perhaps trying multiple times, so don't raise or CM will retry usually forever
156 DockerClient._logger.debug("notification exec_create() succeeded")
158 for attempts_remaining in range(11,-1,-1):
160 result = self._client.exec_start(exec_id=result['Id'])
161 except Exception as e:
162 DockerClient._logger.debug("notification exec_start() got exception {}: {!s}".format(type(e).__name__, e))
163 if attempts_remaining == 0:
164 DockerClient._logger.warn("aborting notification exec_start() because exception {}: {!s}".format(type(e).__name__, e))
165 return str(e) # don't raise or CM will retry usually forever
166 # raise DockerClientError(e)
170 DockerClient._logger.debug("notification exec_start() succeeded")
172 DockerClient._logger.info("Pass to docker exec {} {} {}".format(
173 container_id, cmd, result))