summaryrefslogtreecommitdiffstats
path: root/python-dockering/dockering/core.py
blob: d82e346f767ff08ab117e7aa8098f14ceccd2ff7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
# org.onap.dcae
# ================================================================================
# Copyright (c) 2017 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=========================================================
#
# ECOMP is a trademark and service mark of AT&T Intellectual Property.

import json
import docker
import requests
from dockering.exceptions import DockerError, DockerConnectionError
from dockering import config_building as cb
from dockering import utils


def create_client(hostname, port, reauth=False, logins=[]):
    """Create Docker client

    Args:
    -----
    reauth: (boolean) Forces reauthentication e.g. Docker login
    """
    base_url = "tcp://{0}:{1}".format(hostname, port)
    try:
        client = docker.Client(base_url=base_url)

        for dcl in logins:
            dcl["reauth"] = reauth
            client.login(**dcl)

        return client
    except requests.exceptions.ConnectionError as e:
        raise DockerConnectionError(str(e))


def create_container_using_config(client, service_component_name, container_config):
    try:
        image_name = container_config["Image"]

        if not client.images(image_name):
            def parse_pull_response(response):
                """Pull response is a giant string of JSON messages concatentated
                by `\r\n`. This method returns back those messages in the form of
                list of dicts."""
                # NOTE: There's a trailing `\r\n` so the last element is empty
                # string. Remove that.
                return list(map(json.loads, response.split("\r\n")[:-1]))

            def get_error_message(response):
                """Attempts to pull out and return an error message from parsed
                response if it exists else return None"""
                return response[-1].get("error", None)

            # TODO: Implement this as verbose?
            # for resp in client.pull(image, stream=True, decode=True):
            response = parse_pull_response(client.pull(image_name))
            error_message = get_error_message(response)

            if error_message:
                raise DockerError("Error pulling Docker image: {0}".format(error_message))
            else:
                utils.logger.info("Pulled Docker image: {0}".format(image_name))

        return client.create_container_from_config(container_config,
                service_component_name)
    except requests.exceptions.ConnectionError as e:
        # This separates connection failures so that caller can decide what to do.
        # Underlying errors this inspired were socket.errors that are sourced
        # from http://www.virtsync.com/c-error-codes-include-errno
        raise DockerConnectionError(str(e))
    except Exception as e:
        raise DockerError(str(e))


def create_container(client, image_name, service_component_name, envs,
        host_config_params):
    """Creates Docker container

    Args:
    -----
    envs (dict): dict of environment variables to pass into the docker containers.
        Gets passed into docker-py.create_container call
    host_config_params (dict): Dict of input parameters to the docker-py
        "create_host_config" method call
    """
    config = cb.create_container_config(client, image_name, envs, host_config_params)
    return create_container_using_config(client, service_component_name, config)


def start_container(client, container):
    try:
        # TODO: Have logic to inspect response and through NonRecoverableError
        # when start fails. Docker-py docs don't quickly tell me what the
        # response looks like.
        client.start(container=container["Id"])
        utils.logger.info("Container started: {0}".format(container["Id"]))

        # TODO: Maybe check stats?
        return container["Id"]
    except Exception as e:
        raise DockerError(str(e))


def stop_then_remove_container(client, service_component_name):
    try:
        client.stop(service_component_name)
        client.remove_container(service_component_name)
    except docker.errors.NotFound as e:
        raise DockerError("Container not found: {0}".format(service_component_name))
    except Exception as e:
        raise DockerError(str(e))


def remove_image(client, image_name):
    """Remove the Docker image"""
    try:
        client.remove_image(image_name)
        return True
    except:
        # Failure to remove image is not classified as terrible..for now
        return False


def build_policy_update_cmd(script_path, use_sh=True, msg_type="policy", **kwargs):
    """Build command to execute for policy update"""
    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_policy_update(client, container_id, cmd):
    """Notify Docker container that policy update 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
    """
    try:
        result = client.exec_create(container=container_id,
                cmd=cmd)
        result = client.exec_start(exec_id=result['Id'])

        utils.logger.info("Pass to docker exec {0} {1} {2}".format(
            container_id, cmd, result))

        return result
    except Exception as e:
        raise DockerError(e)