Merge "Adding default False values instead of empty ones"
diff --git a/.kitchen.travis.yml b/.kitchen.travis.yml
deleted file mode 100644
index f847543..0000000
--- a/.kitchen.travis.yml
+++ /dev/null
@@ -1,6 +0,0 @@
-suites:
-
-  - name: <%= ENV['SUITE'] %>
-    provisioner:
-      pillars-from-files:
-        neutron.sls: tests/pillar/<%= ENV['SUITE'] %>.sls
diff --git a/.travis.yml b/.travis.yml
index fa7c5f9..64743ee 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -17,15 +17,16 @@
   - bundle install
 
 env:
-  - PLATFORM=trevorj/salty-whales:xenial SUITE=client_compose
-  - PLATFORM=trevorj/salty-whales:xenial SUITE=client_container
-  - PLATFORM=trevorj/salty-whales:xenial SUITE=host_single
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=client-compose
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=client-container
+  - PLATFORM=trevorj/salty-whales:xenial SUITE=host-single
 
 before_script:
   - make test | tail
 
 script:
-  - KITCHEN_LOCAL_YAML=.kitchen.travis.yml bundle exec kitchen test -t tests/integration
+  - test ! -e .kitchen.yml || bundle exec kitchen converge ${SUITE} || true
+  - test ! -e .kitchen.yml || bundle exec kitchen verify ${SUITE} -t tests/integration
 
 notifications:
   webhooks:
diff --git a/README.rst b/README.rst
index 81cb038..4cbdb11 100644
--- a/README.rst
+++ b/README.rst
@@ -279,6 +279,65 @@
             password: password2
 
 
+Docker container service management
+-----------------------------------
+
+Enforce the service in container is started
+
+.. code-block:: yaml
+
+    contrail_control_started:
+      dockerng_service.start:
+        - container: f020d0d3efa8
+        - service: contrail-control
+
+or
+
+.. code-block:: yaml
+
+    contrail_control_started:
+      dockerng_service.start:
+        - container: contrail_controller
+        - service: contrail-control
+
+
+Enforce the service in container is stoped
+
+.. code-block:: yaml
+
+    contrail_control_stoped:
+      dockerng_service.stop:
+        - container: f020d0d3efa8
+        - service: contrail-control
+
+Enforce the service in container will be restarted
+
+.. code-block:: yaml
+
+    contrail_control_restart:
+      dockerng_service.restart:
+        - container: f020d0d3efa8
+        - service: contrail-control
+
+Enforce the service in container is enabled
+
+.. code-block:: yaml
+
+    contrail_control_enable:
+      dockerng_service.enable:
+        - container: f020d0d3efa8
+        - service: contrail-control
+
+Enforce the service in container is disabled
+
+.. code-block:: yaml
+
+    contrail_control_disable:
+      dockerng_service.disable:
+        - container: f020d0d3efa8
+        - service: contrail-control
+
+
 More Information
 ================
 
diff --git a/_modules/dockerng_service.py b/_modules/dockerng_service.py
new file mode 100644
index 0000000..081b7e0
--- /dev/null
+++ b/_modules/dockerng_service.py
@@ -0,0 +1,99 @@
+#!/usr/bin/python
+# Copyright 2017 Mirantis, Inc.
+#
+# 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.
+
+
+try:
+    import docker
+    HAS_DOCKER = True
+except ImportError:
+    HAS_DOCKER = False
+
+__opts__ = {}
+__virtualname__ = 'dockerng_service'
+
+
+def __virtual__():
+    '''
+    Only load this module if docker library is installed.
+    '''
+    if HAS_DOCKER:
+        return __virtualname__
+    return (False, 'dockerio execution module not loaded: docker python library not available.')
+
+
+def status(container, service):
+    cmd = "systemctl show " + service + " -p ActiveState,SubState,UnitFileState"
+    data =  __salt__['dockerng.run'](container, cmd)
+    data = data.splitlines()
+    result = dict(s.split('=') for s in data)
+    return result
+
+
+def status_retcode(container, service):
+    cmd = "systemctl show " + service + " -p ActiveState,SubState,UnitFileState"
+    data =  __salt__['dockerng.run'](container, cmd)
+    data = data.splitlines()
+    result = dict(s.split('=') for s in data)
+    if result['ActiveState'] == "active" and result['SubState'] == "running":
+        return True
+    return False
+
+
+def restart(container, service):
+    cmd = "systemctl restart " + service
+    data =  __salt__['dockerng.run'](container, cmd)
+    if len(data) > 0:
+        return False
+    return True
+
+
+def stop(container, service):
+    cmd = "systemctl stop " + service
+    data =  __salt__['dockerng.run'](container, cmd)
+    if len(data) > 0:
+        return False
+    return True
+
+
+def start(container, service):
+    cmd = "systemctl start " + service
+    data =  __salt__['dockerng.run'](container, cmd)
+    if len(data) > 0:
+        return False
+    return True
+
+
+def enable(container, service):
+    cmd = "systemctl enable " + service
+    data =  __salt__['dockerng.run'](container, cmd)
+    if len(data) > 0:
+        return False
+    return True
+
+
+def reload(container, service):
+    cmd = "systemctl reload " + service
+    data =  __salt__['dockerng.run'](container, cmd)
+    if len(data) > 0:
+        return False
+    return True
+
+
+def disable(container, service):
+    cmd = "systemctl disable " + service
+    data =  __salt__['dockerng.run'](container, cmd)
+    if len(data) > 0:
+        return False
+    return True
diff --git a/_states/dockerng_service.py b/_states/dockerng_service.py
new file mode 100644
index 0000000..ed13798
--- /dev/null
+++ b/_states/dockerng_service.py
@@ -0,0 +1,297 @@
+#!/usr/bin/python
+# Copyright 2017 Mirantis, Inc.
+#
+# 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.
+'''
+Management of Contrail resources
+================================
+
+:depends:   - vnc_api Python module
+
+
+Enforce the service in container is running
+-------------------------------------------
+
+.. code-block:: yaml
+
+    contrail_control_running:
+      dockerng_service.running:
+        - container: f020d0d3efa8
+        - service: contrail-control
+
+or
+
+.. code-block:: yaml
+
+    contrail_control_running:
+      dockerng_service.running:
+        - container: contrail_controller
+        - service: contrail-control
+
+
+Enforce the service in container is dead
+------------------------------------------
+
+.. code-block:: yaml
+
+    contrail_control_dead:
+      dockerng_service.dead:
+        - container: f020d0d3efa8
+        - service: contrail-control
+
+Enforce the service in container will be restarted
+--------------------------------------------------
+
+.. code-block:: yaml
+
+    contrail_control_restarted:
+      dockerng_service.restarted:
+        - container: f020d0d3efa8
+        - service: contrail-control
+
+Enforce the service in container is enabled
+-------------------------------------------
+
+.. code-block:: yaml
+
+    contrail_control_enabled:
+      dockerng_service.enabled:
+        - container: f020d0d3efa8
+        - service: contrail-control
+
+Enforce the service in container is disabled
+--------------------------------------------
+
+.. code-block:: yaml
+
+    contrail_control_disabled:
+      dockerng_service.disabled:
+        - container: f020d0d3efa8
+        - service: contrail-control
+
+'''
+
+
+def __virtual__():
+    '''
+    Load Contrail module
+    '''
+    return 'dockerng_service'
+
+
+def running(container, service=None, services=None, **kwargs):
+    '''
+    Ensures that the service in the container is running
+
+    :param container:    ID or name of the container
+    :param services:     List of services
+    :param service:      Service name
+    '''
+    ret = {'name': kwargs.get('name', 'dockerng_service.running'),
+           'changes': {},
+           'result': True,
+           'comment': {}
+           }
+
+    if service and not services:
+        services = [service, ]
+
+    for service in services:
+        status = __salt__['dockerng_service.status'](container, service)
+
+        if status['ActiveState'] != "active" and status['SubState'] != "running":
+            if __opts__['test']:
+                ret['result'] = None
+                ret['comment'][service] = " will be started"
+            else:
+                __salt__['dockerng_service.start'](container, service)
+                ret['comment'] = service + " in  " + container + " has been started"
+                ret['changes'] = {service:  "started"}
+
+    return ret
+
+
+def dead(container, service, **kwargs):
+    '''
+    Ensures that the service in the container is dead
+
+    :param container:    ID or name of the container
+    :param service:      Service name
+    '''
+    ret = {'name': service + " in " + container,
+           'changes': {},
+           'result': True,
+           'comment': ''}
+
+    status = __salt__['dockerng_service.status'](container, service)
+
+    if status['ActiveState'] != "inactive" and status['SubState'] != "dead":
+        if __opts__['test']:
+            ret['result'] = None
+            ret['comment'] = service + " in  " + container + " will be stoped"
+            return ret
+
+        __salt__['dockerng_service.stop'](container, service)
+        ret['comment'] = service + " in  " + container + " has been stoped"
+        ret['changes'] = {"new": "stoped", "old": "started"}
+        return ret
+
+    return ret
+
+
+def restarted(container, service, **kwargs):
+    '''
+    Service in the container will be restarted
+
+    :param container:    ID or name of the container
+    :param service:      Service name
+    '''
+    ret = {'name': service + " in " + container,
+           'changes': {},
+           'result': True,
+           'comment': ''}
+
+    if __opts__['test']:
+        ret['result'] = None
+        ret['comment'] = service + " in  " + container + " will be restarted"
+        return ret
+
+    res = __salt__['dockerng_service.restart'](container, service)
+    ret['comment'] = service + " in  " + container + " has been restarted"
+    ret['changes'] = {"status": "restarted"}
+    return ret
+
+
+def enabled(container, service, **kwargs):
+    '''
+    Ensures that the service in the container is enabled
+
+    :param container:    ID or name of the container
+    :param service:      Service name
+    '''
+    ret = {'name': service + " in " + container,
+           'changes': {},
+           'result': True,
+           'comment': ''}
+
+    status = __salt__['dockerng_service.status'](container, service)
+
+    if status['UnitFileState'] != "enabled":
+        if __opts__['test']:
+            ret['result'] = None
+            ret['comment'] = service + " in  " + container + " will be enabled"
+            return ret
+
+        __salt__['dockerng_service.enable'](container, service)
+        ret['comment'] = service + " in  " + container + " has been enabled"
+        ret['changes'] = {"new": "enabled", "old": "disabled"}
+        return ret
+
+    return ret
+
+
+def disabled(container, service, **kwargs):
+    '''
+    Ensures that the service in the container is disabled
+
+    :param container:    ID or name of the container
+    :param service:      Service name
+    '''
+    ret = {'name': service + " in " + container,
+           'changes': {},
+           'result': True,
+           'comment': ''}
+
+    status = __salt__['dockerng_service.status'](container, service)
+
+    if status['UnitFileState'] != "disabled":
+        if __opts__['test']:
+            ret['result'] = None
+            ret['comment'] = service + " in  " + container + " will be disabled"
+            return ret
+
+        __salt__['dockerng_service.disable'](container, service)
+        ret['comment'] = service + " in  " + container + " has been disabled"
+        ret['changes'] = {"old": "enabled", "new": "disabled"}
+        return ret
+
+    return ret
+
+
+def mod_watch(name,
+              contrainer=None,
+              sfun=None,
+              sig=None,
+              reload=False,
+              full_restart=False,
+              init_delay=None,
+              force=False,
+              **kwargs):
+    '''
+    The service watcher, called to invoke the watch command.
+
+    :param name:         The name of the init or rc script used to manage the
+                         service
+    :param sfun:         The original function which triggered the mod_watch
+                         call (`service.running`, for example).
+    :param sig:          The string to search for when looking for the service
+                         process with ps
+    :param reload:       Use reload instead of the default restart (exclusive
+                         option with full_restart, defaults to reload if both
+                         are used)
+    :param full_restart: Use service.full_restart instead of restart
+                         (exclusive option with reload)
+    :param force:        Use service.force_reload instead of reload
+                         (needs reload to be set to True)
+    :param  init_delay:  Add a sleep command (in seconds) before the service is
+                         restarted/reloaded
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': {}}
+
+    service = kwargs.get('service')
+    services = kwargs.get('services')
+    if not services and service:
+        services = [service, ]
+    elif not services and not service:
+        ret['result'] = False
+        ret['comment'] = "Service was not defined"
+        return ret
+
+    container = kwargs.get('container', None)
+    if not container:
+        ret['result'] = False
+        ret['comment'] = "Container was not defined"
+        return ret
+
+    ret['comment'] = {}
+    if sfun == 'running':
+
+        for service in services:
+            status = __salt__['dockerng_service.status'](container, service)
+
+
+            if __opts__['test']:
+                ret['result'] = None
+                ret['comment'][service] = "Services will be restarted"
+                ret['changes'][service] = "will be restarted"
+            else:
+                res = __salt__['dockerng_service.restart'](container, service)
+                ret['comment'] = "Services has been restarted"
+                ret['changes'][service] = "restarted"
+    else:
+        ret['comment'] = 'Unable to trigger watch for dockerng_service.{0}'.format(sfun)
+        ret['result'] = False
+    return ret
diff --git a/debian/control b/debian/control
index ca1f1d1..d6faf77 100644
--- a/debian/control
+++ b/debian/control
@@ -10,6 +10,6 @@
 
 Package: salt-formula-docker
 Architecture: all
-Depends: ${misc:Depends}, salt-master, reclass
+Depends: ${misc:Depends}
 Description: docker salt formula
  Install and configure docker system.
diff --git a/docker/client/init.sls b/docker/client/init.sls
index 6ad6ba0..d80dc11 100644
--- a/docker/client/init.sls
+++ b/docker/client/init.sls
@@ -2,20 +2,22 @@
 {%- if client.get('enabled', False) %}
 
 include:
-  {%- if client.network is defined %}
+  {%- if pillar.docker.client.network is defined %}
   - docker.client.network
   {%- endif %}
+  {%- if pillar.docker.client.container is defined %}
   - docker.client.container
-  {%- if client.compose is defined %}
+  {%- endif %}
+  {%- if pillar.docker.client.compose is defined %}
   - docker.client.compose
   {%- endif %}
-  {%- if client.stack is defined %}
+  {%- if pillar.docker.client.stack is defined %}
   - docker.client.stack
   {%- endif %}
-  {%- if client.registry is defined %}
+  {%- if pillar.docker.client.registry is defined %}
   - docker.client.registry
   {%- endif %}
-  {%- if client.service is defined %}
+  {%- if pillar.docker.client.service is defined %}
   - docker.client.service
   {%- endif %}
 
diff --git a/docker/host.sls b/docker/host.sls
index 816b0c4..da8291e 100644
--- a/docker/host.sls
+++ b/docker/host.sls
@@ -81,10 +81,11 @@
 
 {%- for name,registry in host.registry.iteritems() %}
 
-docker_{{ registry.address }}_login:
+docker_{{ registry.get('address', name) }}_login:
   cmd.run:
-  - name: 'docker login -u {{ registry.user }} -p {{ registry.password }} {{ registry.address }}'
-  - unless: grep {{ registry.address }} /root/.docker/config.json
+  - name: 'docker login -u {{ registry.user }} -p {{ registry.password }}{% if registry.get('address') %} {{ registry.address }}{% endif %}'
+  - user: {{ registry.get('system_user', 'root') }}
+  - unless: grep {{ registry.address|default('https://index.docker.io/v1/') }} {{ salt['user.info'](registry.get('system_user', 'root')).home }}/.docker/config.json
 
 {%- endfor %}
 
diff --git a/docker/meta/fluentd.yml b/docker/meta/fluentd.yml
new file mode 100644
index 0000000..5e41d2a
--- /dev/null
+++ b/docker/meta/fluentd.yml
@@ -0,0 +1,45 @@
+{%- if pillar.get('fluentd', {}).get('agent', {}).get('enabled', False) %}
+{%- set positiondb = pillar.fluentd.agent.dir.positiondb %}
+agent:
+  config:
+    label:
+      docker:
+        input:
+          container:
+            type: tail
+            tag: temp.docker.container.*
+            path: /var/lib/docker/containers/*/*-json.log
+            path_key: log_path
+            pos_file: {{ positiondb }}/docker.container.pos
+            parser:
+              type: json
+              time_format: '%Y-%m-%dT%H:%M:%S.%NZ'
+              keep_time_key: false
+        filter:
+          enrich:
+            tag: 'temp.docker.container.**'
+            type: record_transformer
+            enable_ruby: true
+            remove_keys: log
+            record:
+              - name: severity_label
+                value: INFO
+              - name: Severity
+                value: 6
+              - name: programname
+                value: docker
+              - name: Payload
+                value: ${record['log']}
+        match:
+          cast_service_tag:
+            tag: 'temp.docker.container.**'
+            type: rewrite_tag_filter
+            rule:
+              - name: log_path
+                regexp: '^.*\/(.*)-json\.log$'
+                result: docker.container.$1
+          push_to_default:
+            tag: 'docker.container.*'
+            type: relabel
+            label: default_output
+{%- endif %}
diff --git a/docker/meta/prometheus.yml b/docker/meta/prometheus.yml
index 2d4d7bb..1764543 100644
--- a/docker/meta/prometheus.yml
+++ b/docker/meta/prometheus.yml
@@ -26,31 +26,34 @@
     DockerService{{ camel_case_name }}WarningReplicasNumber:
       if: >-
         count(count_over_time(docker_container_cpu_usage_percent{{ '{' + label_selector + '}' }}[1m])) <= {{ service.deploy.replicas }} * {{ 1 - monitoring.replicas_failed_warning_threshold_percent }}
+      for: 2m
       labels:
         severity: warning
         service: "{{ full_service_name }}"
       annotations:
-        summary: 'Docker Swarm service {{ full_service_name }} invalid number of replicas'
-        description: "{%raw %}{{ $value }}{%- endraw %}/{{ service.deploy.replicas }} replicas are running for the Docker Swarn service '{{ full_service_name }}'."
+        summary: 'Docker Swarm service {{ full_service_name }} invalid number of replicas for 2 minutes'
+        description: "{%raw %}{{ $value }}{%- endraw %}/{{ service.deploy.replicas }} replicas are running for the Docker Swarn service '{{ full_service_name }}' for 2 minutes."
     DockerService{{ camel_case_name }}CriticalReplicasNumber:
       if: >-
         count(count_over_time(docker_container_cpu_usage_percent{{ '{' + label_selector + '}' }}[1m])) <= {{ service.deploy.replicas }} * {{ 1 - monitoring.replicas_failed_critical_threshold_percent }}
+      for: 2m
       labels:
         severity: critical
         service: "{{ full_service_name }}"
       annotations:
-        summary: 'Docker Swarm service {{ full_service_name }} invalid number of replicas'
-        description: "{%raw %}{{ $value }}{%- endraw %}/{{ service.deploy.replicas }} replicas are running for the Docker Swarn service '{{ full_service_name }}'."
+        summary: 'Docker Swarm service {{ full_service_name }} invalid number of replicas for 2 minutes'
+        description: "{%raw %}{{ $value }}{%- endraw %}/{{ service.deploy.replicas }} replicas are running for the Docker Swarn service '{{ full_service_name }}' for 2 minutes."
         {%- endif %}
     DockerService{{ camel_case_name }}ReplicasDown:
       if: >-
         count(count_over_time(docker_container_cpu_usage_percent{{ '{' + label_selector + '}' }}[1m])) == 0 or absent(docker_container_cpu_usage_percent{{ '{' + label_selector + '}' }}) == 1
+      for: 2m
       labels:
         severity: down
         service: "{{ full_service_name }}"
       annotations:
-        summary: 'Docker Swarm service {{ full_service_name }} down'
-        description: "No replicas are running for the Docker Swarn service '{{ full_service_name }}'."
+        summary: 'Docker Swarm service {{ full_service_name }} down for 2 minutes'
+        description: "No replicas are running for the Docker Swarn service '{{ full_service_name }}'. for 2 minutes"
       {%- endif %}
     {%- endfor %}
   {%- endfor %}
diff --git a/docker/meta/sphinx.yml b/docker/meta/sphinx.yml
index 203eb0c..a12c200 100644
--- a/docker/meta/sphinx.yml
+++ b/docker/meta/sphinx.yml
@@ -41,4 +41,13 @@
             - "{{ name }} (image {{ service.image }})"
             {%- endfor %}
         {%- endif %}
+        {%- if client.get('stack', {}) %}
+        stacks:
+          value:
+            {%- for name, stack in client.stack.iteritems() %}
+            {%- for svc_name, service in stack.service.iteritems() %}
+            - "{{ name }}-{{ svc_name }} (image {{ service.image }})"
+            {%- endfor %}
+            {%- endfor %}
+        {%- endif %}
     {%- endif %}
diff --git a/metadata/service/support.yml b/metadata/service/support.yml
index a1c25d8..2127589 100644
--- a/metadata/service/support.yml
+++ b/metadata/service/support.yml
@@ -1,6 +1,8 @@
 parameters:
   docker:
     _support:
+      fluentd:
+        enabled: true
       telegraf:
         enabled: true
       collectd:
diff --git a/tests/run_tests.sh b/tests/run_tests.sh
index 3f42101..9451611 100755
--- a/tests/run_tests.sh
+++ b/tests/run_tests.sh
@@ -110,7 +110,7 @@
 }
 
 salt_run() {
-    [ -e ${VEN_DIR}/bin/activate ] && source ${VENV_DIR}/bin/activate
+    [ -e ${VENV_DIR}/bin/activate ] && source ${VENV_DIR}/bin/activate
     salt-call ${SALT_OPTS} $*
 }