Beispiel #1
0
    def index(self, req):
        """Return a list of all agent builds. Filter by hypervisor."""
        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        hypervisor = None
        agents = []
        if 'hypervisor' in req.GET:
            hypervisor = req.GET['hypervisor']

        builds = objects.AgentList.get_all(context, hypervisor=hypervisor)
        for agent_build in builds:
            agents.append({
                'hypervisor': agent_build.hypervisor,
                'os': agent_build.os,
                'architecture': agent_build.architecture,
                'version': agent_build.version,
                'md5hash': agent_build.md5hash,
                'agent_id': agent_build.id,
                'url': agent_build.url
            })

        return {'agents': agents}
Beispiel #2
0
    def index(self, req):
        """Returns a dict in the format:

        |  {'hosts': [{'host_name': 'some.host.name',
        |     'service': 'cells',
        |     'zone': 'internal'},
        |    {'host_name': 'some.other.host.name',
        |     'service': 'cells',
        |     'zone': 'internal'},
        |    {'host_name': 'some.celly.host.name',
        |     'service': 'cells',
        |     'zone': 'internal'},
        |    {'host_name': 'console1.host.com',
        |     'service': 'consoleauth',
        |     'zone': 'internal'},
        |    {'host_name': 'network1.host.com',
        |     'service': 'network',
        |     'zone': 'internal'},
        |    {'host_name': 'netwwork2.host.com',
        |     'service': 'network',
        |     'zone': 'internal'},
        |    {'host_name': 'compute1.host.com',
        |     'service': 'compute',
        |     'zone': 'patron'},
        |    {'host_name': 'compute2.host.com',
        |     'service': 'compute',
        |     'zone': 'patron'},
        |    {'host_name': 'sched1.host.com',
        |     'service': 'scheduler',
        |     'zone': 'internal'},
        |    {'host_name': 'sched2.host.com',
        |     'service': 'scheduler',
        |     'zone': 'internal'},
        |    {'host_name': 'vol1.host.com',
        |     'service': 'volume',
        |     'zone': 'internal'}]}

        """
        context = req.environ['patron.context']
        authorize(context)

        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks
        patron_context.require_admin_context(context)

        filters = {'disabled': False}
        zone = req.GET.get('zone', None)
        if zone:
            filters['availability_zone'] = zone
        services = self.api.service_get_all(context,
                                            filters=filters,
                                            set_zones=True)
        hosts = []
        for service in services:
            hosts.append({
                'host_name': service['host'],
                'service': service['topic'],
                'zone': service['availability_zone']
            })
        return {'hosts': hosts}
Beispiel #3
0
 def detail(self, req):
     """Returns a detailed list of availability zone."""
     context = req.environ['patron.context']
     authorize_detail(context)
     # NOTE(alex_xu): back-compatible with db layer hard-code admin
     # permission checks.
     patron_context.require_admin_context(context)
     return self._describe_availability_zones_verbose(context)
Beispiel #4
0
 def detail(self, req):
     """Returns a detailed list of availability zone."""
     context = req.environ['patron.context']
     authorize_detail(context)
     # NOTE(alex_xu): back-compatible with db layer hard-code admin
     # permission checks.
     patron_context.require_admin_context(context)
     return self._describe_availability_zones_verbose(context)
Beispiel #5
0
 def index(self, req):
     """Return all migrations in progress."""
     context = req.environ["patron.context"]
     authorize(context, "index")
     # NOTE(alex_xu): back-compatible with db layer hard-code admin
     # permission checks.
     patron_context.require_admin_context(context)
     migrations = self.compute_api.get_migrations(context, req.GET)
     return {"migrations": output(migrations)}
Beispiel #6
0
 def index(self, req):
     """Return all migrations in progress."""
     context = req.environ['patron.context']
     authorize(context, "index")
     # NOTE(alex_xu): back-compatible with db layer hard-code admin
     # permission checks.
     patron_context.require_admin_context(context)
     migrations = self.compute_api.get_migrations(context, req.GET)
     return {'migrations': output(migrations)}
Beispiel #7
0
    def index(self, req):
        """Returns a dict in the format:

        |  {'hosts': [{'host_name': 'some.host.name',
        |     'service': 'cells',
        |     'zone': 'internal'},
        |    {'host_name': 'some.other.host.name',
        |     'service': 'cells',
        |     'zone': 'internal'},
        |    {'host_name': 'some.celly.host.name',
        |     'service': 'cells',
        |     'zone': 'internal'},
        |    {'host_name': 'console1.host.com',
        |     'service': 'consoleauth',
        |     'zone': 'internal'},
        |    {'host_name': 'network1.host.com',
        |     'service': 'network',
        |     'zone': 'internal'},
        |    {'host_name': 'netwwork2.host.com',
        |     'service': 'network',
        |     'zone': 'internal'},
        |    {'host_name': 'compute1.host.com',
        |     'service': 'compute',
        |     'zone': 'patron'},
        |    {'host_name': 'compute2.host.com',
        |     'service': 'compute',
        |     'zone': 'patron'},
        |    {'host_name': 'sched1.host.com',
        |     'service': 'scheduler',
        |     'zone': 'internal'},
        |    {'host_name': 'sched2.host.com',
        |     'service': 'scheduler',
        |     'zone': 'internal'},
        |    {'host_name': 'vol1.host.com',
        |     'service': 'volume',
        |     'zone': 'internal'}]}

        """
        context = req.environ['patron.context']
        authorize(context)

        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks
        patron_context.require_admin_context(context)

        filters = {'disabled': False}
        zone = req.GET.get('zone', None)
        if zone:
            filters['availability_zone'] = zone
        services = self.api.service_get_all(context, filters=filters,
                                            set_zones=True)
        hosts = []
        for service in services:
            hosts.append({'host_name': service['host'],
                          'service': service['topic'],
                          'zone': service['availability_zone']})
        return {'hosts': hosts}
Beispiel #8
0
    def update(self, req, id, body):
        """Enable/Disable scheduling for a service."""
        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks
        patron_context.require_admin_context(context)
        ext_loaded = self.ext_mgr.is_loaded('os-extended-services')
        if id == "enable":
            disabled = False
            status = "enabled"
        elif (id == "disable" or
                (id == "disable-log-reason" and ext_loaded)):
            disabled = True
            status = "disabled"
        else:
            msg = _("Unknown action")
            raise webob.exc.HTTPNotFound(explanation=msg)
        try:
            host = body['host']
            binary = body['binary']
            ret_value = {
                'service': {
                    'host': host,
                    'binary': binary,
                    'status': status,
                },
            }
            status_detail = {
                'disabled': disabled,
                'disabled_reason': None,
            }
            if id == "disable-log-reason":
                reason = body['disabled_reason']
                if not self._is_valid_as_reason(reason):
                    msg = _('The string containing the reason for disabling '
                            'the service contains invalid characters or is '
                            'too long.')
                    raise webob.exc.HTTPBadRequest(explanation=msg)

                status_detail['disabled_reason'] = reason
                ret_value['service']['disabled_reason'] = reason
        except (TypeError, KeyError):
            msg = _('Invalid attribute in the request')
            if 'host' in body and 'binary' in body:
                msg = _('Missing disabled reason field')
            raise webob.exc.HTTPBadRequest(explanation=msg)

        try:
            self.host_api.service_update(context, host, binary, status_detail)
        except exception.HostBinaryNotFound as e:
            raise webob.exc.HTTPNotFound(explanation=e.format_message())

        return ret_value
Beispiel #9
0
 def delete(self, req, id):
     """Deletes an existing agent build."""
     context = req.environ['patron.context']
     authorize(context)
     # NOTE(alex_xu): back-compatible with db layer hard-code admin
     # permission checks.
     patron_context.require_admin_context(context)
     try:
         agent = objects.Agent(context=context, id=id)
         agent.destroy()
     except exception.AgentBuildNotFound as ex:
         raise webob.exc.HTTPNotFound(explanation=ex.format_message())
Beispiel #10
0
    def update(self, req, id, body):
        """Enable/Disable scheduling for a service."""
        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks
        patron_context.require_admin_context(context)
        ext_loaded = self.ext_mgr.is_loaded('os-extended-services')
        if id == "enable":
            disabled = False
            status = "enabled"
        elif (id == "disable" or (id == "disable-log-reason" and ext_loaded)):
            disabled = True
            status = "disabled"
        else:
            msg = _("Unknown action")
            raise webob.exc.HTTPNotFound(explanation=msg)
        try:
            host = body['host']
            binary = body['binary']
            ret_value = {
                'service': {
                    'host': host,
                    'binary': binary,
                    'status': status,
                },
            }
            status_detail = {
                'disabled': disabled,
                'disabled_reason': None,
            }
            if id == "disable-log-reason":
                reason = body['disabled_reason']
                if not self._is_valid_as_reason(reason):
                    msg = _('The string containing the reason for disabling '
                            'the service contains invalid characters or is '
                            'too long.')
                    raise webob.exc.HTTPBadRequest(explanation=msg)

                status_detail['disabled_reason'] = reason
                ret_value['service']['disabled_reason'] = reason
        except (TypeError, KeyError):
            msg = _('Invalid attribute in the request')
            if 'host' in body and 'binary' in body:
                msg = _('Missing disabled reason field')
            raise webob.exc.HTTPBadRequest(explanation=msg)

        try:
            self.host_api.service_update(context, host, binary, status_detail)
        except exception.HostBinaryNotFound as e:
            raise webob.exc.HTTPNotFound(explanation=e.format_message())

        return ret_value
Beispiel #11
0
 def delete(self, req, id):
     """Deletes an existing agent build."""
     context = req.environ['patron.context']
     authorize(context)
     # NOTE(alex_xu): back-compatible with db layer hard-code admin
     # permission checks.
     patron_context.require_admin_context(context)
     try:
         agent = objects.Agent(context=context, id=id)
         agent.destroy()
     except exception.AgentBuildNotFound as ex:
         raise webob.exc.HTTPNotFound(explanation=ex.format_message())
Beispiel #12
0
    def update(self, req, id, body):
        """Update an existing agent build."""
        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        try:
            para = body['para']
            url = para['url']
            md5hash = para['md5hash']
            version = para['version']
        except (TypeError, KeyError) as ex:
            msg = _("Invalid request body: %s") % ex
            raise webob.exc.HTTPBadRequest(explanation=msg)

        try:
            utils.check_string_length(url, 'url', max_length=255)
            utils.check_string_length(md5hash, 'md5hash', max_length=255)
            utils.check_string_length(version, 'version', max_length=255)
        except exception.InvalidInput as exc:
            raise webob.exc.HTTPBadRequest(explanation=exc.format_message())

        try:
            agent = objects.Agent(context=context, id=id)
            agent.obj_reset_changes()
            agent.version = version
            agent.url = url
            agent.md5hash = md5hash
            agent.save()
        except ValueError as ex:
            msg = _("Invalid request body: %s") % ex
            raise webob.exc.HTTPBadRequest(explanation=msg)
        except exception.AgentBuildNotFound as ex:
            raise webob.exc.HTTPNotFound(explanation=ex.format_message())

        # NOTE(alex_xu): The agent_id should be integer that consistent with
        # create/index actions. But parameter 'id' is string type that parsed
        # from url. This is a bug, but because back-compatibility, it can't be
        # fixed for v2 API. This will be fixed after v3 API feature exposed by
        # micro-version in the future. lp bug #1333494
        return {
            "agent": {
                'agent_id': id,
                'version': version,
                'url': url,
                'md5hash': md5hash
            }
        }
Beispiel #13
0
    def delete(self, req, id):
        """Deletes the specified service."""
        if not self.ext_mgr.is_loaded('os-extended-services-delete'):
            raise webob.exc.HTTPMethodNotAllowed()

        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks
        patron_context.require_admin_context(context)

        try:
            self.host_api.service_delete(context, id)
        except exception.ServiceNotFound:
            explanation = _("Service %s not found.") % id
            raise webob.exc.HTTPNotFound(explanation=explanation)
Beispiel #14
0
    def delete(self, req, id):
        """Deletes the specified service."""
        if not self.ext_mgr.is_loaded('os-extended-services-delete'):
            raise webob.exc.HTTPMethodNotAllowed()

        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks
        patron_context.require_admin_context(context)

        try:
            self.host_api.service_delete(context, id)
        except exception.ServiceNotFound:
            explanation = _("Service %s not found.") % id
            raise webob.exc.HTTPNotFound(explanation=explanation)
Beispiel #15
0
    def create(self, req, body):
        """Creates a new agent build."""
        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        try:
            agent = body['agent']
            hypervisor = agent['hypervisor']
            os = agent['os']
            architecture = agent['architecture']
            version = agent['version']
            url = agent['url']
            md5hash = agent['md5hash']
        except (TypeError, KeyError) as ex:
            msg = _("Invalid request body: %s") % ex
            raise webob.exc.HTTPBadRequest(explanation=msg)

        try:
            utils.check_string_length(hypervisor, 'hypervisor', max_length=255)
            utils.check_string_length(os, 'os', max_length=255)
            utils.check_string_length(architecture,
                                      'architecture',
                                      max_length=255)
            utils.check_string_length(version, 'version', max_length=255)
            utils.check_string_length(url, 'url', max_length=255)
            utils.check_string_length(md5hash, 'md5hash', max_length=255)
        except exception.InvalidInput as exc:
            raise webob.exc.HTTPBadRequest(explanation=exc.format_message())

        try:
            agent_obj = objects.Agent(context=context)
            agent_obj.hypervisor = hypervisor
            agent_obj.os = os
            agent_obj.architecture = architecture
            agent_obj.version = version
            agent_obj.url = url
            agent_obj.md5hash = md5hash
            agent_obj.create()
            agent['agent_id'] = agent_obj.id
        except exception.AgentBuildExists as ex:
            raise webob.exc.HTTPConflict(explanation=ex.format_message())
        return {'agent': agent}
Beispiel #16
0
    def index(self, req, flavor_id):
        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        try:
            flavor = objects.Flavor.get_by_flavor_id(context, flavor_id)
        except exception.FlavorNotFound:
            explanation = _("Flavor not found.")
            raise webob.exc.HTTPNotFound(explanation=explanation)

        # public flavor to all projects
        if flavor.is_public:
            explanation = _("Access list not available for public flavors.")
            raise webob.exc.HTTPNotFound(explanation=explanation)

        # private flavor to listed projects only
        return _marshall_flavor_access(flavor)
Beispiel #17
0
    def update(self, req, id, body):
        """Update an existing agent build."""
        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        try:
            para = body['para']
            url = para['url']
            md5hash = para['md5hash']
            version = para['version']
        except (TypeError, KeyError) as ex:
            msg = _("Invalid request body: %s") % ex
            raise webob.exc.HTTPBadRequest(explanation=msg)

        try:
            utils.check_string_length(url, 'url', max_length=255)
            utils.check_string_length(md5hash, 'md5hash', max_length=255)
            utils.check_string_length(version, 'version', max_length=255)
        except exception.InvalidInput as exc:
            raise webob.exc.HTTPBadRequest(explanation=exc.format_message())

        try:
            agent = objects.Agent(context=context, id=id)
            agent.obj_reset_changes()
            agent.version = version
            agent.url = url
            agent.md5hash = md5hash
            agent.save()
        except ValueError as ex:
            msg = _("Invalid request body: %s") % ex
            raise webob.exc.HTTPBadRequest(explanation=msg)
        except exception.AgentBuildNotFound as ex:
            raise webob.exc.HTTPNotFound(explanation=ex.format_message())

        # NOTE(alex_xu): The agent_id should be integer that consistent with
        # create/index actions. But parameter 'id' is string type that parsed
        # from url. This is a bug, but because back-compatibility, it can't be
        # fixed for v2 API. This will be fixed after v3 API feature exposed by
        # micro-version in the future. lp bug #1333494
        return {"agent": {'agent_id': id, 'version': version,
                'url': url, 'md5hash': md5hash}}
Beispiel #18
0
    def create(self, req, body):
        """Creates a new agent build."""
        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        try:
            agent = body['agent']
            hypervisor = agent['hypervisor']
            os = agent['os']
            architecture = agent['architecture']
            version = agent['version']
            url = agent['url']
            md5hash = agent['md5hash']
        except (TypeError, KeyError) as ex:
            msg = _("Invalid request body: %s") % ex
            raise webob.exc.HTTPBadRequest(explanation=msg)

        try:
            utils.check_string_length(hypervisor, 'hypervisor', max_length=255)
            utils.check_string_length(os, 'os', max_length=255)
            utils.check_string_length(architecture, 'architecture',
                                      max_length=255)
            utils.check_string_length(version, 'version', max_length=255)
            utils.check_string_length(url, 'url', max_length=255)
            utils.check_string_length(md5hash, 'md5hash', max_length=255)
        except exception.InvalidInput as exc:
            raise webob.exc.HTTPBadRequest(explanation=exc.format_message())

        try:
            agent_obj = objects.Agent(context=context)
            agent_obj.hypervisor = hypervisor
            agent_obj.os = os
            agent_obj.architecture = architecture
            agent_obj.version = version
            agent_obj.url = url
            agent_obj.md5hash = md5hash
            agent_obj.create()
            agent['agent_id'] = agent_obj.id
        except exception.AgentBuildExists as ex:
            raise webob.exc.HTTPConflict(explanation=ex.format_message())
        return {'agent': agent}
Beispiel #19
0
    def index(self, req, flavor_id):
        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        try:
            flavor = objects.Flavor.get_by_flavor_id(context, flavor_id)
        except exception.FlavorNotFound:
            explanation = _("Flavor not found.")
            raise webob.exc.HTTPNotFound(explanation=explanation)

        # public flavor to all projects
        if flavor.is_public:
            explanation = _("Access list not available for public flavors.")
            raise webob.exc.HTTPNotFound(explanation=explanation)

        # private flavor to listed projects only
        return _marshall_flavor_access(flavor)
Beispiel #20
0
    def _get_services(self, req):
        context = req.environ['patron.context']
        authorize(context)

        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks
        patron_context.require_admin_context(context)

        services = self.host_api.service_get_all(context, set_zones=True)

        host = ''
        if 'host' in req.GET:
            host = req.GET['host']
        binary = ''
        if 'binary' in req.GET:
            binary = req.GET['binary']
        if host:
            services = [s for s in services if s['host'] == host]
        if binary:
            services = [s for s in services if s['binary'] == binary]

        return services
Beispiel #21
0
    def _removeTenantAccess(self, req, id, body):
        context = req.environ['patron.context']
        authorize(context, action="removeTenantAccess")
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        self._check_body(body)

        vals = body['removeTenantAccess']
        tenant = vals.get('tenant')
        if not tenant:
            msg = _("Missing tenant parameter")
            raise webob.exc.HTTPBadRequest(explanation=msg)

        flavor = objects.Flavor(context=context, flavorid=id)
        try:
            flavor.remove_access(tenant)
        except (exception.FlavorNotFound,
                exception.FlavorAccessNotFound) as err:
            raise webob.exc.HTTPNotFound(explanation=err.format_message())

        return _marshall_flavor_access(flavor)
Beispiel #22
0
    def _removeTenantAccess(self, req, id, body):
        context = req.environ['patron.context']
        authorize(context, action="removeTenantAccess")
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        self._check_body(body)

        vals = body['removeTenantAccess']
        tenant = vals.get('tenant')
        if not tenant:
            msg = _("Missing tenant parameter")
            raise webob.exc.HTTPBadRequest(explanation=msg)

        flavor = objects.Flavor(context=context, flavorid=id)
        try:
            flavor.remove_access(tenant)
        except (exception.FlavorNotFound,
                exception.FlavorAccessNotFound) as err:
            raise webob.exc.HTTPNotFound(explanation=err.format_message())

        return _marshall_flavor_access(flavor)
Beispiel #23
0
    def index(self, req):
        """Return a list of all agent builds. Filter by hypervisor."""
        context = req.environ['patron.context']
        authorize(context)
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        hypervisor = None
        agents = []
        if 'hypervisor' in req.GET:
            hypervisor = req.GET['hypervisor']

        builds = objects.AgentList.get_all(context, hypervisor=hypervisor)
        for agent_build in builds:
            agents.append({'hypervisor': agent_build.hypervisor,
                           'os': agent_build.os,
                           'architecture': agent_build.architecture,
                           'version': agent_build.version,
                           'md5hash': agent_build.md5hash,
                           'agent_id': agent_build.id,
                           'url': agent_build.url})

        return {'agents': agents}
Beispiel #24
0
    def _get_services(self, req):
        context = req.environ['patron.context']
        authorize(context)

        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks
        patron_context.require_admin_context(context)

        services = self.host_api.service_get_all(
            context, set_zones=True)

        host = ''
        if 'host' in req.GET:
            host = req.GET['host']
        binary = ''
        if 'binary' in req.GET:
            binary = req.GET['binary']
        if host:
            services = [s for s in services if s['host'] == host]
        if binary:
            services = [s for s in services if s['binary'] == binary]

        return services
Beispiel #25
0
    def _get_audit_task_logs(self, context, begin=None, end=None,
                             before=None):
        """Returns a full log for all instance usage audit tasks on all
           computes.

        :param begin: datetime beginning of audit period to get logs for,
            Defaults to the beginning of the most recently completed
            audit period prior to the 'before' date.
        :param end: datetime ending of audit period to get logs for,
            Defaults to the ending of the most recently completed
            audit period prior to the 'before' date.
        :param before: By default we look for the audit period most recently
            completed before this datetime. Has no effect if both begin and end
            are specified.
        """
        # NOTE(alex_xu): back-compatible with db layer hard-code admin
        # permission checks.
        patron_context.require_admin_context(context)
        defbegin, defend = utils.last_completed_audit_period(before=before)
        if begin is None:
            begin = defbegin
        if end is None:
            end = defend
        task_logs = self.host_api.task_log_get_all(context,
                                                   "instance_usage_audit",
                                                   begin, end)
        # We do this this way to include disabled compute services,
        # which can have instances on them. (mdragon)
        filters = {'topic': CONF.compute_topic}
        services = self.host_api.service_get_all(context, filters=filters)
        hosts = set(serv['host'] for serv in services)
        seen_hosts = set()
        done_hosts = set()
        running_hosts = set()
        total_errors = 0
        total_items = 0
        for tlog in task_logs:
            seen_hosts.add(tlog['host'])
            if tlog['state'] == "DONE":
                done_hosts.add(tlog['host'])
            if tlog['state'] == "RUNNING":
                running_hosts.add(tlog['host'])
            total_errors += tlog['errors']
            total_items += tlog['task_items']
        log = {tl['host']: dict(state=tl['state'],
                                instances=tl['task_items'],
                                errors=tl['errors'],
                                message=tl['message'])
               for tl in task_logs}
        missing_hosts = hosts - seen_hosts
        overall_status = "%s hosts done. %s errors." % (
                    'ALL' if len(done_hosts) == len(hosts)
                    else "%s of %s" % (len(done_hosts), len(hosts)),
                    total_errors)
        return dict(period_beginning=str(begin),
                    period_ending=str(end),
                    num_hosts=len(hosts),
                    num_hosts_done=len(done_hosts),
                    num_hosts_running=len(running_hosts),
                    num_hosts_not_run=len(missing_hosts),
                    hosts_not_run=list(missing_hosts),
                    total_instances=total_items,
                    total_errors=total_errors,
                    overall_status=overall_status,
                    log=log)