1 # ============LICENSE_START=======================================================
3 # ================================================================================
4 # Copyright (c) 2017-2018 AT&T Intellectual Property. All rights reserved.
5 # ================================================================================
6 # Licensed under the Apache License, Version 2.0 (the "License");
7 # you may not use this file except in compliance with the License.
8 # You may obtain a copy of the License at
10 # http://www.apache.org/licenses/LICENSE-2.0
12 # Unless required by applicable law or agreed to in writing, software
13 # distributed under the License is distributed on an "AS IS" BASIS,
14 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 # See the License for the specific language governing permissions and
16 # limitations under the License.
17 # ============LICENSE_END=========================================================
19 # ECOMP is a trademark and service mark of AT&T Intellectual Property.
21 # -*- coding: utf-8 -*-
23 Provides utilities for Docker components
26 from sys import platform
31 import dockering as doc
32 from dcae_cli.util.logger import get_logger
33 from dcae_cli.util.exc import DcaeException
35 dlog = get_logger('Docker')
37 _reg_img = 'gliderlabs/registrator:latest'
38 # TODO: Source this from app's configuration [ONAP URL TBD]
39 _reg_cmd = '-ip {:} consul://make-me-valid:8500'
41 class DockerError(DcaeException):
44 class DockerConstructionError(DcaeException):
48 # Functions to provide envs to pass into Docker containers
50 def _convert_profile_to_docker_envs(profile):
51 """Convert a profile object to Docker environment variables
59 dict of environemnt variables to be used by docker-py
61 profile = profile._asdict()
62 return dict([(key.upper(), value) for key, value in six.iteritems(profile)])
65 def build_envs(profile, docker_config, instance_name):
66 profile_envs = _convert_profile_to_docker_envs(profile)
67 health_envs = doc.create_envs_healthcheck(docker_config)
68 return doc.create_envs(instance_name, profile_envs, health_envs)
71 # Methods to call Docker engine
73 # TODO: Consolidate these two docker client methods. Need ability to invoke local
74 # vs remote Docker engine
76 def get_docker_client(profile, logins=[]):
77 hostname, port = profile.docker_host.split(":")
79 client = doc.create_client(hostname, port, logins=logins)
83 raise DockerError('Could not connect to the Docker daemon. Is it running?')
86 def image_exists(image):
87 '''Returns True if the image exists locally'''
88 client = docker.APIClient(version="auto", **docker.utils.kwargs_from_env())
89 return True if client.images(image) else False
93 '''Infers the IP address of the host running this tool'''
94 if not platform.startswith('linux'):
95 raise DockerError('Non-linux environment detected. Use the --external-ip flag when running Docker components.')
96 ip = socket.gethostbyname(socket.gethostname())
97 dlog.info("Docker host external IP address inferred to be {:}. If this is incorrect, use the --external-ip flag.".format(ip))
101 def _run_container(client, config, name=None, wait=False):
102 '''Runs a container'''
104 info = six.next(iter(client.containers(all=True, filters={'name': "^/{:}$".format(name)})), None)
106 if info['State'] == 'running':
107 dlog.info("Container '{:}' was detected as already running.".format(name))
110 client.remove_container(info['Id'])
112 cont = doc.create_container_using_config(client, name, config)
114 info = client.inspect_container(cont)
115 name = info['Name'][1:] # remove '/' prefix
116 image = config['Image']
117 dlog.info("Running image '{:}' as '{:}'".format(image, name))
122 cont_log = dlog.getChild(name)
124 for msg in client.logs(cont, stream=True):
125 cont_log.info(msg.decode())
127 dlog.info("Container '{:}' exitted suddenly.".format(name))
128 except (KeyboardInterrupt, SystemExit):
129 dlog.info("Stopping container '{:}' and cleaning up...".format(name))
131 client.remove_container(cont)
134 def _run_registrator(client, external_ip=None):
135 '''Ensures that Registrator is running'''
137 ip = _infer_ip() if external_ip is None else external_ip
138 cmd = _reg_cmd.format(ip).split()
140 binds={'/var/run/docker.sock': {'bind': '/tmp/docker.sock'}}
141 hconf = client.create_host_config(binds=binds, network_mode='host')
142 conf = client.create_container_config(image=_reg_img, command=cmd, host_config=hconf)
144 _run_container(client, conf, name='registrator', wait=False)
147 # TODO: Need to revisit and reimplement _run_registrator(client, external_ip)
153 def deploy_component(profile, image, instance_name, docker_config, should_wait=False,
155 """Deploy Docker component
157 This calls runs a Docker container detached. The assumption is that the Docker
158 host already has registrator running.
160 TODO: Split out the wait functionality
164 logins (list): List of objects where the objects are each a docker login of
167 {"registry": .., "username":.., "password":.. }
171 Dict that is the result from a Docker inspect call
173 ports = docker_config.get("ports", None)
174 hcp = doc.add_host_config_params_ports(ports=ports)
175 volumes = docker_config.get("volumes", None)
176 hcp = doc.add_host_config_params_volumes(volumes=volumes, host_config_params=hcp)
177 # Thankfully passing in an IP will return back an IP
178 dh = profile.docker_host.split(":")[0]
179 _, _, dhips = socket.gethostbyname_ex(dh)
182 hcp = doc.add_host_config_params_dns(dhips[0], hcp)
184 raise DockerConstructionError("Could not resolve the docker hostname:{0}".format(dh))
186 envs = build_envs(profile, docker_config, instance_name)
187 client = get_docker_client(profile, logins=logins)
189 config = doc.create_container_config(client, image, envs, hcp)
191 return _run_container(client, config, name=instance_name, wait=should_wait)
194 def undeploy_component(client, image, instance_name):
195 """Undeploy Docker component
197 TODO: Handle error scenarios. Look into:
198 * no container found error - docker.errors.NotFound
199 * failure to remove image - docker.errors.APIError: 409 Client Error
200 * retry, check for still running container
204 True if the container and associated image has been removed False otherwise
207 client.remove_container(instance_name, force=True)
208 client.remove_image(image)
210 except Exception as e:
211 dlog.error("Error while undeploying Docker container/image: {0}".format(e))
214 def reconfigure(client, instance_name, command):
215 """ Execute the Reconfig script in the Docker container """
217 # 'command' has 3 parts in a list (1 Command and 2 ARGs)
218 exec_Id = client.exec_create(container=instance_name, cmd=command)
220 exec_start_resp = client.exec_start(exec_Id, stream=True)
222 # Using a 'single' generator response to solve issue of 'start_exec' returning control after 6 minutes
223 for response in exec_start_resp:
224 dlog.info("Reconfig Script execution response: {:}".format(response))
225 exec_start_resp.close()