blob: 08fd1c0c25f42259aeaed9a8e589b9ef6721604c [file] [log] [blame]
# 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.
"""
Abstraction in Docker container configuration
"""
import six
from dockering import utils
from dockering.exceptions import DockerConstructionError
#
# Methods to build container envs
#
def create_envs_healthcheck(docker_config, default_interval="15s",
default_timeout="1s"):
"""Extract health check environment variables for Docker containers
Parameters
----------
docker_config: dict where there's an entry called "healthcheck"
Returns
-------
dict of Docker envs for healthcheck
"""
# TODO: This has been shamefully lifted from the dcae-cli and should probably
# shared as a library. The unit tests are there. The difference is that
# there are defaults that are passed in here but the defaults should really
# come from the component spec definition. The issue is that nothing forwards
# those defaults.
envs = dict()
hc = docker_config["healthcheck"]
# NOTE: For the multiple port, schema scenario, you can explicitly set port
# to schema. For example if image EXPOSE 8080, SERVICE_8080_CHECK_HTTP works.
# https://github.com/gliderlabs/registrator/issues/311
if hc["type"] == "http":
envs["SERVICE_CHECK_HTTP"] = hc["endpoint"]
elif hc["type"] == "https":
# WATCH: HTTPS health checks don't work. Seems like Registrator bug.
# Submitted issue https://github.com/gliderlabs/registrator/issues/516
envs["SERVICE_CHECK_HTTPS"] = hc["endpoint"]
# Went with one of the suggestiong from the posted issue above. This
# author is skeptical whether https healthchecks will ever work with
# server cert verification because the hostname is actually the ip
# address.
envs["SERVICE_CHECK_TLS_SKIP_VERIFY"] = "true"
utils.logger.warn("Https-based health checks may not work because Registrator issue #516")
elif hc["type"] == "script":
envs["SERVICE_CHECK_SCRIPT"] = hc["script"]
elif hc["type"] == "docker":
# Note this is only supported in the AT&T open source version of registrator
envs["SERVICE_CHECK_DOCKER_SCRIPT"] = hc["script"]
else:
# You should never get here but not having an else block feels weird
raise DockerConstructionError("Unexpected health check type: {0}".format(hc["type"]))
envs["SERVICE_CHECK_INTERVAL"] = hc.get("interval", default_interval)
envs["SERVICE_CHECK_TIMEOUT"] = hc.get("timeout", default_timeout)
return envs
def create_envs(service_component_name, *envs):
"""Merge all environment variables maps
Creates a complete environment variables map that is to be used for creating
the container.
Args:
-----
envs: Arbitrary list of dicts where each dict is of the structure:
{
<environment variable name>: <environment variable value>,
<environment variable name>: <environment variable value>,
...
}
Returns:
--------
Dict of all environment variable name to environment variable value
"""
master_envs = { "HOSTNAME": service_component_name,
# For Registrator to register with generated name and not the
# image name
"SERVICE_NAME": service_component_name }
for envs_map in envs:
master_envs.update(envs_map)
return master_envs
#
# Methods for volume bindings
#
def _parse_volumes_param(volumes):
"""Parse volumes details for Docker containers from blueprint
Takes in a list of dicts that contains Docker volume info and
transforms them into docker-py compliant (unflattened) data structures.
Look for the `volumes` parameters under the `run` method on
[this page](https://docker-py.readthedocs.io/en/stable/containers.html)
Args:
volumes (list): List of
{
"host": {
"path": <target path on host>
},
"container": {
"bind": <target path in container>,
"mode": <read/write>
}
}
Returns:
dict of the form
{
<target path on host>: {
"bind": <target path in container>,
"mode": <read/write>
}
}
if volumes is None then returns None
"""
if volumes:
return dict([ (vol["host"]["path"], vol["container"]) for vol in volumes ])
else:
return None
#
# Utility methods used to help build the inputs to create the host_config
#
def add_host_config_params_volumes(volumes=None, host_config_params=None):
"""Add volumes input params
Args:
-----
volumes (list): List of
{
"host": {
"path": <target path on host>
},
"container": {
"bind": <target path in container>,
"mode": <read/write>
}
}
host_config_params (dict): Target dict to accumulate host config inputs
Returns:
--------
Updated host_config_params
"""
# TODO: USE parse_volumes_param here!
if host_config_params == None:
host_config_params = {}
host_config_params["binds"] = _parse_volumes_param(volumes)
return host_config_params
def add_host_config_params_ports(ports=None, host_config_params=None):
"""Add ports input params
Args:
-----
ports (list): Each port mapping entry is of the form
"<container ports>:<host port>"
host_config_params (dict): Target dict to accumulate host config inputs
Returns:
--------
Updated host_config_params
"""
if host_config_params == None:
host_config_params = {}
if ports:
ports = [ port.split(":") for port in ports ]
port_bindings = { port[0]: { "HostPort": port[1] } for port in ports }
host_config_params["port_bindings"] = port_bindings
host_config_params["publish_all_ports"] = False
else:
host_config_params["publish_all_ports"] = True
return host_config_params
def add_host_config_params_dns(docker_host, host_config_params=None):
"""Add dns input params
This is not a generic implementation. This method will setup dns with the
expectation that a local consul agent is running on the docker host and will
service the dns requests.
Args:
-----
docker_host (string): Docker host ip address which will be used as the dns server
host_config_params (dict): Target dict to accumulate host config inputs
Returns:
--------
Updated host_config_params
"""
if host_config_params == None:
host_config_params = {}
host_config_params["dns"] = [docker_host]
host_config_params["dns_search"] = ["service.consul"]
host_config_params["extra_hosts"] = { "consul": docker_host }
return host_config_params
def _get_container_ports(host_config_params):
"""Grab list of container ports from host config params"""
if "port_bindings" in host_config_params:
return list(six.iterkeys(host_config_params["port_bindings"]))
else:
return None
def create_container_config(client, image, envs, host_config_params, tty=False):
"""Create docker container config
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
"""
# This is the 1.10.6 approach to binding volumes
# http://docker-py.readthedocs.io/en/1.10.6/volumes.html
volumes = host_config_params.get("bind", None)
target_volumes = [ target["bind"] for target in volumes.values() ] \
if volumes else None
host_config = client.create_host_config(**host_config_params)
ports = _get_container_ports(host_config_params)
command = "" # This is required...
config = client.create_container_config(image, command, detach=True, tty=tty,
host_config=host_config, ports=ports,
environment=envs, volumes=target_volumes)
utils.logger.info("Docker container config: {0}".format(config))
return config