text: # coding=utf-8
  
  # Copyright 2013 Hewlett-Packard Development Company, L.P.
  # 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.
  """
  Client side of the conductor RPC API.
  """
  
  import random
  
  from oslo import messaging
  
  from ironic.common import exception
  from ironic.common import hash_ring as hash
  from ironic.common import rpc
  from ironic.conductor import manager
  from ironic.objects import base as objects_base
  
  
  class ConductorAPI(object):
      """Client side of the conductor RPC API.
  
      API version history:
  
          1.0 - Initial version.
                Included get_node_power_status
          1.1 - Added update_node and start_power_state_change.
          1.2 - Added vendor_passhthru.
          1.3 - Rename start_power_state_change to change_node_power_state.
          1.4 - Added do_node_deploy and do_node_tear_down.
          1.5 - Added validate_driver_interfaces.
          1.6 - change_node_power_state, do_node_deploy and do_node_tear_down
                accept node id instead of node object.
          1.7 - Added topic parameter to RPC methods.
          1.8 - Added change_node_maintenance_mode.
          1.9 - Added destroy_node.
          1.10 - Remove get_node_power_state
          1.11 - Added get_console_information, set_console_mode.
          1.12 - validate_vendor_action, do_vendor_action replaced by single
                vendor_passthru method.
          1.13 - Added update_port.
          1.14 - Added driver_vendor_passthru.
          1.15 - Added rebuild parameter to do_node_deploy.
  
      """
  
      # NOTE(rloo): This must be in sync with manager.ConductorManager's.
      RPC_API_VERSION = '1.15'
  
      def __init__(self, topic=None):
          super(ConductorAPI, self).__init__()
          self.topic = topic
          if self.topic is None:
              self.topic = manager.MANAGER_TOPIC
  
          target = messaging.Target(topic=self.topic,
                                    version='1.0')
          serializer = objects_base.IronicObjectSerializer()
          self.client = rpc.get_client(target,
                                       version_cap=self.RPC_API_VERSION,
                                       serializer=serializer)
          self.ring_manager = hash.HashRingManager()
  
      def get_topic_for(self, node):
          """Get the RPC topic for the conductor service which the node
          is mapped to.
  
          :param node: a node object.
          :returns: an RPC topic string.
          :raises: NoValidHost
  
          """
          try:
              ring = self.ring_manager.get_hash_ring(node.driver)
              dest = ring.get_hosts(node.uuid)
              return self.topic + "." + dest[0]
          except exception.DriverNotFound:
              reason = (_('No conductor service registered which supports '
                          'driver %s.') % node.driver)
              raise exception.NoValidHost(reason=reason)
  
      def get_topic_for_driver(self, driver_name):
          """Get an RPC topic which will route messages to a conductor which
          supports the specified driver. A conductor is selected at
          random from the set of qualified conductors.
  
          :param driver_name: the name of the driver to route to.
          :returns: an RPC topic string.
          :raises: DriverNotFound
  
          """
          hash_ring = self.ring_manager.get_hash_ring(driver_name)
          host = random.choice(hash_ring.hosts)
          return self.topic + "." + host
  
      def update_node(self, context, node_obj, topic=None):
          """Synchronously, have a conductor update the node's information.
  
          Update the node's information in the database and return a node object.
          The conductor will lock the node while it validates the supplied
          information. If driver_info is passed, it will be validated by
          the core drivers. If instance_uuid is passed, it will be set or unset
          only if the node is properly configured.
  
          Note that power_state should not be passed via this method.
          Use change_node_power_state for initiating driver actions.
  
          :param context: request context.
          :param node_obj: a changed (but not saved) node object.
          :param topic: RPC topic. Defaults to self.topic.
          :returns: updated node object, including all fields.
  
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.1')
          return cctxt.call(context, 'update_node', node_obj=node_obj)
  
      def change_node_power_state(self, context, node_id, new_state, topic=None):
          """Synchronously, acquire lock and start the conductor background task
          to change power state of a node.
  
          :param context: request context.
          :param node_id: node id or uuid.
          :param new_state: one of ironic.common.states power state values
          :param topic: RPC topic. Defaults to self.topic.
          :raises: NoFreeConductorWorker when there is no free worker to start
                   async task.
  
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.6')
          return cctxt.call(context, 'change_node_power_state', node_id=node_id,
                            new_state=new_state)
  
      def vendor_passthru(self, context, node_id, driver_method, info,
                          topic=None):
          """Synchronously, acquire lock, validate given parameters and start
          the conductor background task for specified vendor action.
  
          :param context: request context.
          :param node_id: node id or uuid.
          :param driver_method: name of method for driver.
          :param info: info for node driver.
          :param topic: RPC topic. Defaults to self.topic.
          :raises: InvalidParameterValue if supplied info is not valid.
          :raises: UnsupportedDriverExtension if current driver does not have
                   vendor interface.
          :raises: NoFreeConductorWorker when there is no free worker to start
                   async task.
  
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.12')
          return cctxt.call(context, 'vendor_passthru', node_id=node_id,
                            driver_method=driver_method, info=info)
  
      def driver_vendor_passthru(self, context, driver_name, driver_method, info,
                          topic=None):
          """Pass vendor-specific calls which don't specify a node to a driver.
  
          :param context: request context.
          :param driver_name: name of the driver on which to call the method.
          :param driver_method: name of the vendor method, for use by the driver.
          :param info: data to pass through to the driver.
          :param topic: RPC topic. Defaults to self.topic.
          :raises: InvalidParameterValue for parameter errors.
          :raises: UnsupportedDriverExtension if the driver doesn't have a vendor
                   interface, or if the vendor interface does not support the
                   specified driver_method.
  
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.14')
          return cctxt.call(context, 'driver_vendor_passthru',
                            driver_name=driver_name,
                            driver_method=driver_method,
                            info=info)
  
      def do_node_deploy(self, context, node_id, rebuild, topic=None):
          """Signal to conductor service to perform a deployment.
  
          :param context: request context.
          :param node_id: node id or uuid.
          :param rebuild: True if this is a rebuild request.
          :param topic: RPC topic. Defaults to self.topic.
          :raises: InstanceDeployFailure
          :raises: InvalidParameterValue if validation fails
          :raises: NoFreeConductorWorker when there is no free worker to start
                   async task.
  
          The node must already be configured and in the appropriate
          undeployed state before this method is called.
  
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.15')
          return cctxt.call(context, 'do_node_deploy', node_id=node_id,
                            rebuild=rebuild)
  
      def do_node_tear_down(self, context, node_id, topic=None):
          """Signal to conductor service to tear down a deployment.
  
          :param context: request context.
          :param node_id: node id or uuid.
          :param topic: RPC topic. Defaults to self.topic.
          :raises: InstanceDeployFailure
          :raises: InvalidParameterValue if validation fails
          :raises: NoFreeConductorWorker when there is no free worker to start
                   async task.
  
          The node must already be configured and in the appropriate
          deployed state before this method is called.
  
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.6')
          return cctxt.call(context, 'do_node_tear_down', node_id=node_id)
  
      def validate_driver_interfaces(self, context, node_id, topic=None):
          """Validate the `core` and `standardized` interfaces for drivers.
  
          :param context: request context.
          :param node_id: node id or uuid.
          :param topic: RPC topic. Defaults to self.topic.
          :returns: a dictionary containing the results of each
                    interface validation.
  
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.5')
          return cctxt.call(context, 'validate_driver_interfaces',
                            node_id=node_id)
  
      def change_node_maintenance_mode(self, context, node_id, mode, topic=None):
          """Set node maintenance mode on or off.
  
          :param context: request context.
          :param node_id: node id or uuid.
          :param mode: True or False.
          :param topic: RPC topic. Defaults to self.topic.
          :returns: a node object.
          :raises: NodeMaintenanceFailure.
  
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.8')
          return cctxt.call(context, 'change_node_maintenance_mode',
                            node_id=node_id, mode=mode)
  
      def destroy_node(self, context, node_id, topic=None):
          """Delete a node.
  
          :param context: request context.
          :param node_id: node id or uuid.
          :raises: NodeLocked if node is locked by another conductor.
          :raises: NodeAssociated if the node contains an instance
              associated with it.
          :raises: NodeInWrongPowerState if the node is not powered off.
  
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.9')
          return cctxt.call(context, 'destroy_node', node_id=node_id)
  
      def get_console_information(self, context, node_id, topic=None):
          """Get connection information about the console.
  
          :param context: request context.
          :param node_id: node id or uuid.
          :param topic: RPC topic. Defaults to self.topic.
          :raises: UnsupportedDriverExtension if the node's driver doesn't
                   support console.
          :raises: InvalidParameterValue when the wrong driver info is specified.
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.11')
          return cctxt.call(context, 'get_console_information', node_id=node_id)
  
      def set_console_mode(self, context, node_id, enabled, topic=None):
          """Enable/Disable the console.
  
          :param context: request context.
          :param node_id: node id or uuid.
          :param topic: RPC topic. Defaults to self.topic.
          :param enabled: Boolean value; whether the console is enabled or
                          disabled.
          :raises: UnsupportedDriverExtension if the node's driver doesn't
                   support console.
          :raises: InvalidParameterValue when the wrong driver info is specified.
          :raises: NoFreeConductorWorker when there is no free worker to start
                   async task.
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.11')
          return cctxt.call(context, 'set_console_mode', node_id=node_id,
                            enabled=enabled)
  
      def update_port(self, context, port_obj, topic=None):
          """Synchronously, have a conductor update the port's information.
  
          Update the port's information in the database and return a port object.
          The conductor will lock related node and trigger specific driver
          actions if they are needed.
  
          :param context: request context.
          :param port_obj: a changed (but not saved) port object.
          :param topic: RPC topic. Defaults to self.topic.
          :returns: updated port object, including all fields.
  
          """
          cctxt = self.client.prepare(topic=topic or self.topic, version='1.13')
          return cctxt.call(context, 'update_port', port_obj=port_obj)
  