Tommy Carpenter | 81b9ed7 | 2017-08-23 11:21:44 -0400 | [diff] [blame^] | 1 | # org.onap.dcae |
| 2 | # ================================================================================ |
| 3 | # Copyright (c) 2017 AT&T Intellectual Property. All rights reserved. |
| 4 | # ================================================================================ |
| 5 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | # you may not use this file except in compliance with the License. |
| 7 | # You may obtain a copy of the License at |
| 8 | # |
| 9 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | # |
| 11 | # Unless required by applicable law or agreed to in writing, software |
| 12 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | # See the License for the specific language governing permissions and |
| 15 | # limitations under the License. |
| 16 | # ============LICENSE_END========================================================= |
| 17 | # |
| 18 | # ECOMP is a trademark and service mark of AT&T Intellectual Property. |
| 19 | |
| 20 | import json |
| 21 | import docker |
| 22 | import requests |
| 23 | from dockering.exceptions import DockerError, DockerConnectionError |
| 24 | from dockering import config_building as cb |
| 25 | from dockering import utils |
| 26 | |
| 27 | |
| 28 | # TODO: Replace default for logins to source it from Consul..perhaps |
| 29 | |
| 30 | def create_client(hostname, port, reauth=False, logins=[]): |
| 31 | """Create Docker client |
| 32 | |
| 33 | Args: |
| 34 | ----- |
| 35 | reauth: (boolean) Forces reauthentication e.g. Docker login |
| 36 | """ |
| 37 | base_url = "tcp://{0}:{1}".format(hostname, port) |
| 38 | try: |
| 39 | client = docker.Client(base_url=base_url) |
| 40 | |
| 41 | for dcl in logins: |
| 42 | dcl["reauth"] = reauth |
| 43 | client.login(**dcl) |
| 44 | |
| 45 | return client |
| 46 | except requests.exceptions.ConnectionError as e: |
| 47 | raise DockerConnectionError(str(e)) |
| 48 | |
| 49 | |
| 50 | def create_container_using_config(client, service_component_name, container_config): |
| 51 | try: |
| 52 | image_name = container_config["Image"] |
| 53 | |
| 54 | if not client.images(image_name): |
| 55 | def parse_pull_response(response): |
| 56 | """Pull response is a giant string of JSON messages concatentated |
| 57 | by `\r\n`. This method returns back those messages in the form of |
| 58 | list of dicts.""" |
| 59 | # NOTE: There's a trailing `\r\n` so the last element is empty |
| 60 | # string. Remove that. |
| 61 | return list(map(json.loads, response.split("\r\n")[:-1])) |
| 62 | |
| 63 | def get_error_message(response): |
| 64 | """Attempts to pull out and return an error message from parsed |
| 65 | response if it exists else return None""" |
| 66 | return response[-1].get("error", None) |
| 67 | |
| 68 | # TODO: Implement this as verbose? |
| 69 | # for resp in client.pull(image, stream=True, decode=True): |
| 70 | response = parse_pull_response(client.pull(image_name)) |
| 71 | error_message = get_error_message(response) |
| 72 | |
| 73 | if error_message: |
| 74 | raise DockerError("Error pulling Docker image: {0}".format(error_message)) |
| 75 | else: |
| 76 | utils.logger.info("Pulled Docker image: {0}".format(image_name)) |
| 77 | |
| 78 | return client.create_container_from_config(container_config, |
| 79 | service_component_name) |
| 80 | except requests.exceptions.ConnectionError as e: |
| 81 | # This separates connection failures so that caller can decide what to do. |
| 82 | # Underlying errors this inspired were socket.errors that are sourced |
| 83 | # from http://www.virtsync.com/c-error-codes-include-errno |
| 84 | raise DockerConnectionError(str(e)) |
| 85 | except Exception as e: |
| 86 | raise DockerError(str(e)) |
| 87 | |
| 88 | |
| 89 | def create_container(client, image_name, service_component_name, envs, |
| 90 | host_config_params): |
| 91 | """Creates Docker container |
| 92 | |
| 93 | Args: |
| 94 | ----- |
| 95 | envs (dict): dict of environment variables to pass into the docker containers. |
| 96 | Gets passed into docker-py.create_container call |
| 97 | host_config_params (dict): Dict of input parameters to the docker-py |
| 98 | "create_host_config" method call |
| 99 | """ |
| 100 | config = cb.create_container_config(client, image_name, envs, host_config_params) |
| 101 | return create_container_using_config(client, service_component_name, config) |
| 102 | |
| 103 | |
| 104 | def start_container(client, container): |
| 105 | try: |
| 106 | # TODO: Have logic to inspect response and through NonRecoverableError |
| 107 | # when start fails. Docker-py docs don't quickly tell me what the |
| 108 | # response looks like. |
| 109 | response = client.start(container=container["Id"]) |
| 110 | utils.logger.info("Container started: {0}".format(container["Id"])) |
| 111 | |
| 112 | # TODO: Maybe check stats? |
| 113 | return container["Id"] |
| 114 | except Exception as e: |
| 115 | raise DockerError(str(e)) |
| 116 | |
| 117 | |
| 118 | def stop_then_remove_container(client, service_component_name): |
| 119 | try: |
| 120 | client.stop(service_component_name) |
| 121 | client.remove_container(service_component_name) |
| 122 | except docker.errors.NotFound as e: |
| 123 | raise DockerError("Container not found: {0}".format(service_component_name)) |
| 124 | except Exception as e: |
| 125 | raise DockerError(str(e)) |
| 126 | |
| 127 | |
| 128 | def remove_image(client, image_name): |
| 129 | """Remove the Docker image""" |
| 130 | try: |
| 131 | client.remove_image(image_name) |
| 132 | return True |
| 133 | except: |
| 134 | # Failure to remove image is not classified as terrible..for now |
| 135 | return False |
| 136 | |