text: # Copyright (c) 2011 OpenStack Foundation
  # 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.
  
  """The hosts admin extension."""
  
  from oslo_log import log as logging
  import webob.exc
  
  from nova.api.openstack import extensions
  from nova import compute
  from nova import context as nova_context
  from nova import exception
  from nova.i18n import _
  from nova.i18n import _LI
  from nova import objects
  
  LOG = logging.getLogger(__name__)
  authorize = extensions.extension_authorizer('compute', 'hosts')
  
  
  class HostController(object):
      """The Hosts API controller for the OpenStack API."""
      def __init__(self):
          self.api = compute.HostAPI()
          super(HostController, self).__init__()
  
      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': 'nova'},
          |    {'host_name': 'compute2.host.com',
          |     'service': 'compute',
          |     'zone': 'nova'},
          |    {'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['nova.context']
          authorize(context)
  
          # NOTE(alex_xu): back-compatible with db layer hard-code admin
          # permission checks
          nova_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}
  
      def update(self, req, id, body):
          """Updates a specified body.
  
          :param body: example format {'status': 'enable',
                                       'maintenance_mode': 'enable'}
          """
          def read_enabled(orig_val, msg):
              """Checks a specified orig_val and returns True for 'enabled'
              and False for 'disabled'.
  
              :param orig_val: A string with either 'enable' or 'disable'. May
                               be surrounded by whitespace, and case doesn't
                               matter
              :param msg: The message to be passed to HTTPBadRequest. A single
                          %s will be replaced with orig_val.
              """
              val = orig_val.strip().lower()
              if val == "enable":
                  return True
              elif val == "disable":
                  return False
              else:
                  raise webob.exc.HTTPBadRequest(explanation=msg % orig_val)
          context = req.environ['nova.context']
          authorize(context)
          # See what the user wants to 'update'
          params = {k.strip().lower(): v for k, v in body.iteritems()}
          orig_status = status = params.pop('status', None)
          orig_maint_mode = maint_mode = params.pop('maintenance_mode', None)
          # Validate the request
          if len(params) > 0:
              # Some extra param was passed. Fail.
              explanation = _("Invalid update setting: '%s'") % params.keys()[0]
              raise webob.exc.HTTPBadRequest(explanation=explanation)
          if orig_status is not None:
              status = read_enabled(orig_status, _("Invalid status: '%s'"))
          if orig_maint_mode is not None:
              maint_mode = read_enabled(orig_maint_mode, _("Invalid mode: '%s'"))
          if status is None and maint_mode is None:
              explanation = _("'status' or 'maintenance_mode' needed for "
                              "host update")
              raise webob.exc.HTTPBadRequest(explanation=explanation)
          # Make the calls and merge the results
          result = {'host': id}
          if status is not None:
              result['status'] = self._set_enabled_status(context, id, status)
          if maint_mode is not None:
              result['maintenance_mode'] = self._set_host_maintenance(context,
                  id, maint_mode)
          return result
  
      def _set_host_maintenance(self, context, host_name, mode=True):
          """Start/Stop host maintenance window. On start, it triggers
          guest VMs evacuation.
          """
          LOG.info(_LI("Putting host %(host_name)s in maintenance mode "
                      "%(mode)s."),
                    {'host_name': host_name, 'mode': mode})
          try:
              result = self.api.set_host_maintenance(context, host_name, mode)
          except NotImplementedError:
              msg = _("Virt driver does not implement host maintenance mode.")
              raise webob.exc.HTTPNotImplemented(explanation=msg)
          except exception.NotFound as e:
              raise webob.exc.HTTPNotFound(explanation=e.format_message())
          except exception.ComputeServiceUnavailable as e:
              raise webob.exc.HTTPBadRequest(explanation=e.format_message())
          if result not in ("on_maintenance", "off_maintenance"):
              raise webob.exc.HTTPBadRequest(explanation=result)
          return result
  
      def _set_enabled_status(self, context, host_name, enabled):
          """Sets the specified host's ability to accept new instances.
  
          :param enabled: a boolean - if False no new VMs will be able to start
          on the host
          """
          if enabled:
              LOG.info(_LI("Enabling host %s.") % host_name)
          else:
              LOG.info(_LI("Disabling host %s.") % host_name)
          try:
              result = self.api.set_host_enabled(context, host_name=host_name,
                      enabled=enabled)
          except NotImplementedError:
              msg = _("Virt driver does not implement host disabled status.")
              raise webob.exc.HTTPNotImplemented(explanation=msg)
          except exception.NotFound as e:
              raise webob.exc.HTTPNotFound(explanation=e.format_message())
          except exception.ComputeServiceUnavailable as e:
              raise webob.exc.HTTPBadRequest(explanation=e.format_message())
          if result not in ("enabled", "disabled"):
              raise webob.exc.HTTPBadRequest(explanation=result)
          return result
  
      def _host_power_action(self, req, host_name, action):
          """Reboots, shuts down or powers up the host."""
          context = req.environ['nova.context']
          authorize(context)
          try:
              result = self.api.host_power_action(context, host_name=host_name,
                      action=action)
          except NotImplementedError:
              msg = _("Virt driver does not implement host power management.")
              raise webob.exc.HTTPNotImplemented(explanation=msg)
          except exception.NotFound as e:
              raise webob.exc.HTTPNotFound(explanation=e.format_message())
          except exception.ComputeServiceUnavailable as e:
              raise webob.exc.HTTPBadRequest(explanation=e.format_message())
          return {"host": host_name, "power_action": result}
  
      def startup(self, req, id):
          return self._host_power_action(req, host_name=id, action="startup")
  
      def shutdown(self, req, id):
          return self._host_power_action(req, host_name=id, action="shutdown")
  
      def reboot(self, req, id):
          return self._host_power_action(req, host_name=id, action="reboot")
  
      @staticmethod
      def _get_total_resources(host_name, compute_node):
          return {'resource': {'host': host_name,
                               'project': '(total)',
                               'cpu': compute_node['vcpus'],
                               'memory_mb': compute_node['memory_mb'],
                               'disk_gb': compute_node['local_gb']}}
  
      @staticmethod
      def _get_used_now_resources(host_name, compute_node):
          return {'resource': {'host': host_name,
                               'project': '(used_now)',
                               'cpu': compute_node['vcpus_used'],
                               'memory_mb': compute_node['memory_mb_used'],
                               'disk_gb': compute_node['local_gb_used']}}
  
      @staticmethod
      def _get_resource_totals_from_instances(host_name, instances):
          cpu_sum = 0
          mem_sum = 0
          hdd_sum = 0
          for instance in instances:
              cpu_sum += instance['vcpus']
              mem_sum += instance['memory_mb']
              hdd_sum += instance['root_gb'] + instance['ephemeral_gb']
  
          return {'resource': {'host': host_name,
                               'project': '(used_max)',
                               'cpu': cpu_sum,
                               'memory_mb': mem_sum,
                               'disk_gb': hdd_sum}}
  
      @staticmethod
      def _get_resources_by_project(host_name, instances):
          # Getting usage resource per project
          project_map = {}
          for instance in instances:
              resource = project_map.setdefault(instance['project_id'],
                      {'host': host_name,
                       'project': instance['project_id'],
                       'cpu': 0,
                       'memory_mb': 0,
                       'disk_gb': 0})
              resource['cpu'] += instance['vcpus']
              resource['memory_mb'] += instance['memory_mb']
              resource['disk_gb'] += (instance['root_gb'] +
                                      instance['ephemeral_gb'])
          return project_map
  
      def show(self, req, id):
          """Shows the physical/usage resource given by hosts.
  
          :param id: hostname
          :returns: expected to use HostShowTemplate.
              ex.::
  
                  {'host': {'resource':D},..}
                  D: {'host': 'hostname','project': 'admin',
                      'cpu': 1, 'memory_mb': 2048, 'disk_gb': 30}
          """
          context = req.environ['nova.context']
          host_name = id
          try:
              compute_node = (
                  objects.ComputeNode.get_first_node_by_host_for_old_compat(
                      context, host_name))
          except exception.NotFound as e:
              raise webob.exc.HTTPNotFound(explanation=e.format_message())
          except exception.AdminRequired:
              msg = _("Describe-resource is admin only functionality")
              raise webob.exc.HTTPForbidden(explanation=msg)
          instances = self.api.instance_get_all_by_host(context, host_name)
          resources = [self._get_total_resources(host_name, compute_node)]
          resources.append(self._get_used_now_resources(host_name,
                                                        compute_node))
          resources.append(self._get_resource_totals_from_instances(host_name,
                                                                    instances))
          by_proj_resources = self._get_resources_by_project(host_name,
                                                             instances)
          for resource in by_proj_resources.itervalues():
              resources.append({'resource': resource})
          return {'host': resources}
  
  
  class Hosts(extensions.ExtensionDescriptor):
      """Admin-only host administration."""
  
      name = "Hosts"
      alias = "os-hosts"
      namespace = "http://docs.openstack.org/compute/ext/hosts/api/v1.1"
      updated = "2011-06-29T00:00:00Z"
  
      def get_resources(self):
          resources = [extensions.ResourceExtension('os-hosts',
                  HostController(),
                  collection_actions={'update': 'PUT'},
                  member_actions={"startup": "GET", "shutdown": "GET",
                          "reboot": "GET"})]
          return resources
  