From 16c46b7fecdd6f1097d00ae4849671cf21029656 Mon Sep 17 00:00:00 2001 From: aditi Date: Tue, 17 Jan 2017 04:49:54 +0530 Subject: [PATCH] Improve the container status This patch will add a new field status_detail to container which will return detail status about container as docker ps command. Change-Id: Ia825281c6e92d3b25407d4d01b3968c65e5ec79d Closes-Bug: #1654010 --- .../controllers/v1/views/containers_view.py | 3 +- zun/container/docker/driver.py | 67 ++++++++++++++++--- .../ad43a2179cf2_add_status_detail.py | 34 ++++++++++ zun/db/sqlalchemy/models.py | 1 + zun/objects/container.py | 4 +- zun/tests/unit/db/utils.py | 1 + 6 files changed, 98 insertions(+), 12 deletions(-) create mode 100644 zun/db/sqlalchemy/alembic/versions/ad43a2179cf2_add_status_detail.py diff --git a/zun/api/controllers/v1/views/containers_view.py b/zun/api/controllers/v1/views/containers_view.py index 859495ec5..8e1d87c3b 100644 --- a/zun/api/controllers/v1/views/containers_view.py +++ b/zun/api/controllers/v1/views/containers_view.py @@ -34,7 +34,8 @@ _basic_keys = ( 'addresses', 'image_pull_policy', 'host', - 'restart_policy' + 'restart_policy', + 'status_detail' ) diff --git a/zun/container/docker/driver.py b/zun/container/docker/driver.py index 0bdde7fd9..0465b7a2c 100644 --- a/zun/container/docker/driver.py +++ b/zun/container/docker/driver.py @@ -11,10 +11,12 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime import six from docker import errors from oslo_log import log as logging +from oslo_utils import timeutils from zun.common import exception from zun.common.i18n import _ @@ -131,30 +133,75 @@ class DockerDriver(driver.ContainerDriver): self._populate_container(container, response) return container + def format_status_detail(self, status_time): + try: + st = datetime.datetime.strptime((status_time[:-4]), + '%Y-%m-%dT%H:%M:%S.%f') + except ValueError as e: + LOG.exception(_LE("Error on parse {} : {}").format(status_time, e)) + return + delta = timeutils.utcnow() - st + time_dict = {} + time_dict['days'] = delta.days + time_dict['hours'] = delta.seconds//3600 + time_dict['minutes'] = (delta.seconds % 3600)//60 + time_dict['seconds'] = delta.seconds + if time_dict['days']: + return '{} days'.format(time_dict['days']) + if time_dict['hours']: + return '{} hours'.format(time_dict['hours']) + if time_dict['minutes']: + return '{} mins'.format(time_dict['minutes']) + if time_dict['seconds']: + return '{} seconds'.format(time_dict['seconds']) + return + def _populate_container(self, container, response): status = response.get('State') if status: + status_detail = '' if status.get('Error') is True: container.status = fields.ContainerStatus.ERROR + status_detail = self.format_status_detail( + status.get('FinishedAt')) + container.status_detail = "Exited({}) {} ago " \ + "(error)".format(status.get('ExitCode'), status_detail) elif status.get('Paused'): container.status = fields.ContainerStatus.PAUSED + status_detail = self.format_status_detail( + status.get('StartedAt')) + container.status_detail = "Up {} (paused)".format( + status_detail) elif status.get('Running'): container.status = fields.ContainerStatus.RUNNING + status_detail = self.format_status_detail( + status.get('StartedAt')) + container.status_detail = "Up {}".format( + status_detail) else: container.status = fields.ContainerStatus.STOPPED + status_detail = self.format_status_detail( + status.get('FinishedAt')) + container.status_detail = "Exited({}) {} ago ".format( + status.get('ExitCode'), status_detail) + if status_detail is None: + container.status_detail = None config = response.get('Config') if config: - # populate hostname - container.hostname = config.get('Hostname') - # populate ports - ports = [] - exposed_ports = config.get('ExposedPorts') - if exposed_ports: - for key in exposed_ports: - port = key.split('/')[0] - ports.append(int(port)) - container.ports = ports + self._populate_hostname_and_ports(container, config) + + def _populate_hostname_and_ports(self, container, config): + # populate hostname + container.hostname = config.get('Hostname') + # populate ports + ports = [] + exposed_ports = config.get('ExposedPorts') + if exposed_ports: + for key in exposed_ports: + port = key.split('/')[0] + ports.append(int(port)) + container.ports = ports @check_container_id def reboot(self, container, timeout): diff --git a/zun/db/sqlalchemy/alembic/versions/ad43a2179cf2_add_status_detail.py b/zun/db/sqlalchemy/alembic/versions/ad43a2179cf2_add_status_detail.py new file mode 100644 index 000000000..c6f872f2e --- /dev/null +++ b/zun/db/sqlalchemy/alembic/versions/ad43a2179cf2_add_status_detail.py @@ -0,0 +1,34 @@ +# 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. + + +"""add_status_detail + +Revision ID: ad43a2179cf2 +Revises: bbcfa910a8a5 +Create Date: 2017-01-17 03:14:50.739446 + +""" + +# revision identifiers, used by Alembic. +revision = 'ad43a2179cf2' +down_revision = 'bbcfa910a8a5' +branch_labels = None +depends_on = None + +from alembic import op +import sqlalchemy as sa + + +def upgrade(): + op.add_column('container', sa.Column('status_detail', + sa.String(length=50), nullable=True)) diff --git a/zun/db/sqlalchemy/models.py b/zun/db/sqlalchemy/models.py index 3426b582f..d748e26b9 100644 --- a/zun/db/sqlalchemy/models.py +++ b/zun/db/sqlalchemy/models.py @@ -147,6 +147,7 @@ class Container(Base): image_pull_policy = Column(Text, nullable=True) host = Column(String(255)) restart_policy = Column(JSONEncodedDict) + status_detail = Column(String(50)) class Image(Base): diff --git a/zun/objects/container.py b/zun/objects/container.py index 085c55e11..c41e690cd 100644 --- a/zun/objects/container.py +++ b/zun/objects/container.py @@ -28,7 +28,8 @@ class Container(base.ZunPersistentObject, base.ZunObject): # Version 1.6: Add addresses column # Version 1.7: Add host column # Version 1.8: Add restart_policy - VERSION = '1.8' + # Version 1.9: Add status_detail column + VERSION = '1.9' fields = { 'id': fields.IntegerField(), @@ -54,6 +55,7 @@ class Container(base.ZunPersistentObject, base.ZunObject): 'image_pull_policy': fields.StringField(nullable=True), 'host': fields.StringField(nullable=True), 'restart_policy': fields.DictOfStringsField(nullable=True), + 'status_detail': fields.StringField(nullable=True) } @staticmethod diff --git a/zun/tests/unit/db/utils.py b/zun/tests/unit/db/utils.py index 6c038950c..6a81c7847 100644 --- a/zun/tests/unit/db/utils.py +++ b/zun/tests/unit/db/utils.py @@ -59,6 +59,7 @@ def get_test_container(**kw): 'host': kw.get('host', 'localhost'), 'restart_policy': kw.get('restart_policy', {'Name': 'no', 'MaximumRetryCount': '0'}), + 'status_detail': kw.get('status_detail', 'up from 5 hours'), }