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 | |
Tommy Carpenter | 81b9ed7 | 2017-08-23 11:21:44 -0400 | [diff] [blame] | 28 | def create_client(hostname, port, reauth=False, logins=[]): |
| 29 | """Create Docker client |
| 30 | |
| 31 | Args: |
| 32 | ----- |
| 33 | reauth: (boolean) Forces reauthentication e.g. Docker login |
| 34 | """ |
| 35 | base_url = "tcp://{0}:{1}".format(hostname, port) |
| 36 | try: |
| 37 | client = docker.Client(base_url=base_url) |
| 38 | |
| 39 | for dcl in logins: |
| 40 | dcl["reauth"] = reauth |
| 41 | client.login(**dcl) |
| 42 | |
| 43 | return client |
| 44 | except requests.exceptions.ConnectionError as e: |
| 45 | raise DockerConnectionError(str(e)) |
| 46 | |
| 47 | |
| 48 | def create_container_using_config(client, service_component_name, container_config): |
| 49 | try: |
| 50 | image_name = container_config["Image"] |
| 51 | |
| 52 | if not client.images(image_name): |
| 53 | def parse_pull_response(response): |
| 54 | """Pull response is a giant string of JSON messages concatentated |
| 55 | by `\r\n`. This method returns back those messages in the form of |
| 56 | list of dicts.""" |
| 57 | # NOTE: There's a trailing `\r\n` so the last element is empty |
| 58 | # string. Remove that. |
| 59 | return list(map(json.loads, response.split("\r\n")[:-1])) |
| 60 | |
| 61 | def get_error_message(response): |
| 62 | """Attempts to pull out and return an error message from parsed |
| 63 | response if it exists else return None""" |
| 64 | return response[-1].get("error", None) |
| 65 | |
| 66 | # TODO: Implement this as verbose? |
| 67 | # for resp in client.pull(image, stream=True, decode=True): |
| 68 | response = parse_pull_response(client.pull(image_name)) |
| 69 | error_message = get_error_message(response) |
| 70 | |
| 71 | if error_message: |
| 72 | raise DockerError("Error pulling Docker image: {0}".format(error_message)) |
| 73 | else: |
| 74 | utils.logger.info("Pulled Docker image: {0}".format(image_name)) |
| 75 | |
| 76 | return client.create_container_from_config(container_config, |
| 77 | service_component_name) |
| 78 | except requests.exceptions.ConnectionError as e: |
| 79 | # This separates connection failures so that caller can decide what to do. |
| 80 | # Underlying errors this inspired were socket.errors that are sourced |
| 81 | # from http://www.virtsync.com/c-error-codes-include-errno |
| 82 | raise DockerConnectionError(str(e)) |
| 83 | except Exception as e: |
| 84 | raise DockerError(str(e)) |
| 85 | |
| 86 | |
| 87 | def create_container(client, image_name, service_component_name, envs, |
| 88 | host_config_params): |
| 89 | """Creates Docker container |
| 90 | |
| 91 | Args: |
| 92 | ----- |
| 93 | envs (dict): dict of environment variables to pass into the docker containers. |
| 94 | Gets passed into docker-py.create_container call |
| 95 | host_config_params (dict): Dict of input parameters to the docker-py |
| 96 | "create_host_config" method call |
| 97 | """ |
| 98 | config = cb.create_container_config(client, image_name, envs, host_config_params) |
| 99 | return create_container_using_config(client, service_component_name, config) |
| 100 | |
| 101 | |
| 102 | def start_container(client, container): |
| 103 | try: |
| 104 | # TODO: Have logic to inspect response and through NonRecoverableError |
| 105 | # when start fails. Docker-py docs don't quickly tell me what the |
| 106 | # response looks like. |
Michael Hwang | e11be5d | 2017-09-21 12:19:32 -0400 | [diff] [blame] | 107 | client.start(container=container["Id"]) |
Tommy Carpenter | 81b9ed7 | 2017-08-23 11:21:44 -0400 | [diff] [blame] | 108 | utils.logger.info("Container started: {0}".format(container["Id"])) |
| 109 | |
| 110 | # TODO: Maybe check stats? |
| 111 | return container["Id"] |
| 112 | except Exception as e: |
| 113 | raise DockerError(str(e)) |
| 114 | |
| 115 | |
| 116 | def stop_then_remove_container(client, service_component_name): |
| 117 | try: |
| 118 | client.stop(service_component_name) |
| 119 | client.remove_container(service_component_name) |
| 120 | except docker.errors.NotFound as e: |
| 121 | raise DockerError("Container not found: {0}".format(service_component_name)) |
| 122 | except Exception as e: |
| 123 | raise DockerError(str(e)) |
| 124 | |
| 125 | |
| 126 | def remove_image(client, image_name): |
| 127 | """Remove the Docker image""" |
| 128 | try: |
| 129 | client.remove_image(image_name) |
| 130 | return True |
| 131 | except: |
| 132 | # Failure to remove image is not classified as terrible..for now |
| 133 | return False |
| 134 | |
Michael Hwang | f5ce303 | 2017-09-07 16:00:09 -0400 | [diff] [blame] | 135 | |
| 136 | def build_policy_update_cmd(script_path, use_sh=True, msg_type="policy", **kwargs): |
| 137 | """Build command to execute for policy update""" |
| 138 | data = json.dumps(kwargs or {}) |
| 139 | |
| 140 | if use_sh: |
| 141 | return ['/bin/sh', script_path, msg_type, data] |
| 142 | else: |
| 143 | return [script_path, msg_type, data] |
| 144 | |
| 145 | def notify_for_policy_update(client, container_id, cmd): |
| 146 | """Notify Docker container that policy update occurred |
| 147 | |
| 148 | Notify the Docker container by doing Docker exec of passed-in command |
| 149 | |
| 150 | Args: |
| 151 | ----- |
| 152 | container_id: (string) |
| 153 | cmd: (list) of strings each entry being part of the command |
| 154 | """ |
| 155 | try: |
| 156 | result = client.exec_create(container=container_id, |
| 157 | cmd=cmd) |
| 158 | result = client.exec_start(exec_id=result['Id']) |
| 159 | |
| 160 | utils.logger.info("Pass to docker exec {0} {1} {2}".format( |
| 161 | container_id, cmd, result)) |
| 162 | |
| 163 | return result |
| 164 | except Exception as e: |
| 165 | raise DockerError(e) |