| # 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 |
| |