crowbar/crowbar-core

View on GitHub
crowbar_framework/app/controllers/barclamp_controller.rb

Summary

Maintainability
F
1 wk
Test Coverage
#
# Copyright 2011-2013, Dell
# Copyright 2013-2014, SUSE LINUX Products GmbH
#
# 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.
#

require "chef"
require "json"

class BarclampController < ApplicationController
  wrap_parameters false

  skip_before_filter :enforce_installer, if: proc { Crowbar::Installer.initial_chef_client? }
  skip_before_filter :upgrade, only: [:transition]
  before_filter :initialize_service
  before_filter :controller_to_barclamp

  # define parameter groups for apipie
  def_param_group :proposal do
    param :id, String, desc: "Proposal name", required: true
    param :barclamp, String, desc: "Name of the barclamp", required: true
  end

  def controller_to_barclamp
    @bc_name = params[:barclamp] || params[:controller]
    @service_object.bc_name = @bc_name
  end

  self.help_contents = Array.new(superclass.help_contents)

  add_help(:barclamp_index)
  api :GET, "/crowbar", "Returns a list of string names and descriptions for all barclamps"
  header "Accept", "application/json", required: true
  example '
  {
    "ceilometer": "Installation for Ceilometer",
    "deployer": "Initial classification system for the Crowbar environment ",
    "crowbar": "Self-referential barclamp enabling other barclamps",
    "keystone": "Centralized authentication and authorization service for OpenStack",
    "ipmi": "The default proposal for the ipmi barclamp",
    "logging": "Centralized logging system based on syslog",
    "rabbitmq": "Installation for rabbitmq",
    "nova": "installs and configures the Openstack Nova component. It relies upon the network and glance barclamps for normal operation.",
    "glance": "Glance service (image registry and delivery service) for the cloud",
    "provisioner": "The roles and recipes to set up the provisioning server and a base environment for all nodes",
    "barbican": "Key and Secret Management Service for OpenStack",
    "database": "Installation for Database",
    "horizon": "User Interface for OpenStack projects (code name Horizon)",
    "manila": "Installation for Manila",
    "ntp": "Common NTP service for the cluster. An NTP server or servers can be specified and all other nodes will be clients of them.",
    "ceph": "Distributed object store and file system",
    "network": "Instantiates network interfaces on the crowbar managed systems. Also manages the address pool",
    "neutron": "API-enabled, pluggable virtual network service for OpenStack",
    "tempest": "provides a tempest installation",
    "updater": "System package updater",
    "trove": "Sets up OpenStack Trove Database Service",
    "swift": "part of Openstack, and provides a distributed blob storage",
    "nfs_client": "Access remote filesystems by utilizing NFS",
    "dns": "Manages the DNS subsystem for the cluster",
    "heat": "Installation for Heat",
    "pacemaker": "Installation for Pacemaker",
    "suse_manager_client": "Register systems as SUSE Manager clients",
    "cinder": "Installation for Cinder",
    "magnum": "Sets up OpenStack Magnum Containers as a Service"
  }
  '
  def barclamp_index
    @barclamps = ServiceObject.all
    respond_to do |format|
      format.html { raise ActionController::RoutingError.new("Not Found") }
      format.xml  { render xml: @barclamps }
      format.json { render json: @barclamps }
    end
  end

  add_help(:versions)
  api :GET, "/crowbar/:barclamp_name",
    "Returns the API version of a barclamp"
  header "Accept", "application/json", required: true
  example '
  {
    "versions": [
      "1.0"
    ]
  }
  '
  def versions
    ret = @service_object.versions
    return render text: ret[1], status: ret[0] if ret[0] != 200
    render json: ret[1]
  end

  add_help(:transition, [:id, :name, :state], [:get,:post])
  api [:GET, :POST], "/crowbar/:barclamp/1.0/transition/:id",
    "Informs the barclamp instance of a change of state in the specified node - The GET is
    supported here to allow for the limited function environment of the installation system."
  header "Accept", "application/json", required: true
  param :id, String, desc: "Proposal name", required: true
  param :name, String, desc: "Name of the node transitioning", required: true
  param :state, String, desc: "State of the node transitioning", required: true
  error 404, "Node not found"
  error 500, "Transitioning failed, details are in the respose"
  def transition
    id = params[:id]       # Provisioner id
    state = params[:state] # State of node transitioning
    name = params[:name] # Name or alias of node transitioning

    unless state == "discovering"
      node = Node.find_node_by_name_or_alias(name)
      raise ActionController::RoutingError, "Not Found" if node.nil? # raise an error if not found
      name = node.name # replace the alias with the real name
    end

    if Crowbar::State.valid_transition_state?(state)
      status, response = @service_object.transition(id, name, state)
      if status != 200
        render text: response, status: status
      else
        # Be backward compatible with barclamps returning a node hash, passing
        # them intact.
        if response[:name]
          render json: Node.find_by_name(response[:name]).to_hash
        else
          render json: response
        end
      end
    else
      render text: "State '#{state}' is not valid.", status: 400
    end
  end

  add_help(:show,[:id])
  api :GET, "/crowbar/:barclamp/1.0/:id",
    "Returns a document describing the instance"
  header "Accept", "application/json", required: true
  example '
  {
    "id": "dns-default",
    "description": "Manages the DNS subsystem for the cluster",
    "attributes": {
      "dns": {
        "domain": "cloud.crowbar.com",
        "forwarders": [
          "192.168.124.1"
        ],
        "allow_transfer": [],
        "nameservers": [],
        "records": {
          "multi-dns": {
            "ips": [
              "10.11.12.13"
            ]
          }
        },
        "auto_assign_server": true
      }
    },
    "deployment": {
      "dns": {
        "crowbar-revision": 3,
        "crowbar-applied": true,
        "schema-revision": 100,
        "element_states": {
          "dns-server": [
            "readying",
            "ready",
            "applying"
          ],
          "dns-client": [
            "readying",
            "ready",
            "applying"
          ]
        },
        "elements": {
          "dns-server": [
            "crowbar.crowbar.com"
          ],
          "dns-client": [
            "d52-54-77-77-77-01.crowbar.com",
            "d52-54-77-77-77-02.crowbar.com"
          ]
        },
        "element_order": [
          [
            "dns-server"
          ],
          [
            "dns-client"
          ]
        ],
        "element_run_list_order": {
          "dns-server": 30,
          "dns-client": 31
        },
        "config": {
          "environment": "dns-config-default",
          "mode": "full",
          "transitions": true,
          "transition_list": [
            "installed",
            "readying"
          ]
        },
        "crowbar-committing": true,
        "crowbar-status": "success",
        "crowbar-failed": ""
      }
    }
  }
  '
  param_group :proposal
  error 404, "Proposal not found"
  def show
    ret = @service_object.show_active params[:id]
    @role = ret[1]
    Rails.logger.debug "Role #{ret.inspect}"
    respond_to do |format|
      format.html {
        return redirect_to show_proposal_path controller: @bc_name, id: params[:id] if ret[0] != 200
        render template: "barclamp/show"
      }
      format.xml  {
        return render text: @role, status: ret[0] if ret[0] != 200
        render xml: role_to_proposal(@role, @bc_name)
      }
      # FIXME: this json endpoint can only be accessed when explicitly sending a json header
      format.json {
        return render text: @role, status: ret[0] if ret[0] != 200
        render json: role_to_proposal(@role, @bc_name)
      }
    end
  end

  add_help(:delete,[:id],[:delete])
  api :DELETE, "/crowbar/:barclamp/1.0/:id",
    "Delete will deactivate and remove the proposal"
  header "Accept", "application/json", required: true
  param_group :proposal
  error 500, "Failed to deactivate proposal"
  def delete
    params[:id] = params[:id] || params[:name]
    ret = [500, "Server Problem"]
    begin
      ret = @service_object.destroy_active(params[:id])
      set_flash(ret, "proposal.actions.delete_%s")
    rescue StandardError => e
      Rails.logger.error "Failed to deactivate proposal: #{e.message}\n#{e.backtrace.join("\n")}"
      flash[:alert] = t("proposal.actions.delete_failure") + e.message
      ret = [500, flash[:alert]]
    end

    respond_to do |format|
      format.html {
        redirect_to barclamp_modules_path(id: @bc_name)
      }
      format.xml  {
        return render text: ret[1], status: ret[0] if ret[0] != 200
        render xml: {}
      }
      format.json {
        return render text: ret[1], status: ret[0] if ret[0] != 200
        render json: {}
      }
    end
  end

  api :GET, "/crowbar/:controller/1.0/elements",
    "Returns a list of roles that a node could be assigned to"
  header "Accept", "application/json", required: true
  param :controller, String, desc: "Name of the controller (barclamp)", required: true
  example '
  [
    "dns-server",
    "dns-client"
  ]
  '
  add_help(:elements)
  def elements
    ret = @service_object.elements
    return render text: ret[1], status: ret[0] if ret[0] != 200
    render json: ret[1]
  end

  add_help(:element_info,[:id])
  api :GET, "/crowbar/:controller/1.0/elements/:id",
    "Returns a list of nodes that can be assigned to that element"
  header "Accept", "application/json", required: true
  param :id, String, desc: "Proposal name", required: true
  param :controller, String, desc: "Name of the controller (barclamp)", required: true
  example '
  [
    "d52-54-77-77-77-01.crowbar.com",
    "d52-54-77-77-77-02.crowbar.com",
    "crowbar.crowbar.com"
  ]
  '
  def element_info
    ret = @service_object.element_info(params[:id])
    return render text: ret[1], status: ret[0] if ret[0] != 200
    render json: ret[1]
  end

  add_help(:index)
  api :GET, "/crowbar/:barclamp/1.0",
    "Returns a list of names for the ids of instances"
  header "Accept", "application/json", required: true
  param :barclamp, String, desc: "Name of the barclamp", required: true
  example '
  [
    "default"
  ]
  '
  def index
    respond_to do |format|
      format.html {
        @title ||= "#{@bc_name.titlecase} #{t('barclamp.index.members')}"
        @count = -1
        members = {}
        list = BarclampCatalog.members(@bc_name)
        barclamps = BarclampCatalog.barclamps
        i = 0
        (list || {}).each { |bc, order| members[bc] = { "description" => barclamps[bc]["description"], "order"=>order || 99999} if !barclamps[bc].nil? and barclamps[bc]["user_managed"] }
        @modules = get_proposals_from_barclamps(members).sort_by { |k,v| "%05d%s" % [v[:order], k] }
        render "barclamp/index"
      }
      format.xml  {
        ret = @service_object.list_active
        @roles = ret[1]
        return render text: @roles, status: ret[0] if ret[0] != 200
        render xml: @roles
      }
      format.json {
        ret = @service_object.list_active
        @roles = ret[1]
        return render text: @roles, status: ret[0] if ret[0] != 200
        render json: @roles
      }
    end
  end

  add_help(:modules)
  api :GET, "/crowbar/modules/1.0",
    "Returns a list of barclamp data mainly used by the UI"
  header "Accept", "application/json", required: true
  example '
  [
    [
      "crowbar",
      {
        "description": "Self-referential barclamp enabling other barclamps",
        "order": 0,
        "proposals": {
          "default": {
            "id": 1,
            "description": "Self-referential barclamp enabling other barclamps",
            "status": "ready",
            "active": true
          }
        },
        "expand": false,
        "members": 12,
        "allow_multiple_proposals": false,
        "suggested_proposal_name": "proposal"
      }
    ],
    [
      "deployer",
      {
        "description": "Deployment Management",
        "order": 10,
        "proposals": {
          "default": {
            "id": 2,
            "description": "Initial classification system for the Crowbar environment ",
            "status": "ready",
            "active": true
          }
        },
        "expand": false,
        "members": 0,
        "allow_multiple_proposals": false,
        "suggested_proposal_name": "proposal"
      }
    ],
    ...
  ]
  '
  def modules
    @title = I18n.t("barclamp.modules.title")
    @count = 0
    barclamps = BarclampCatalog.barclamps.dup.delete_if { |bc, props| !props["user_managed"] }
    @modules = get_proposals_from_barclamps(barclamps).sort_by { |k,v| "%05d%s" % [v[:order], k] }
    respond_to do |format|
      format.html { render "index" }
      format.xml  { render xml: @modules }
      format.json { render json: @modules }
    end
  end

  #
  # List proposals
  # Return a list of available proposals
  # GET /crowbar/<barclamp-name>/<version>/proposals
  #
  add_help(:proposals, [], [:get])
  api :GET, "/crowbar/:barclamp/1.0/proposals",
    "Returns a list of available proposals"
  header "Accept", "application/json", required: true
  param :barclamp, String, desc: "Name of the barclamp", required: true
  example '
  [
    "default"
  ]
  '
  error 404, "Proposals of barclamp not found"
  def proposals
    code, message = @service_object.proposals

    raise Crowbar::Error::NotFound if code == 404
    respond_to do |format|
      case code
      when 200
        format.json do
          render json: message
        end
        format.html do
          @proposals = message.map do |proposal|
            Proposal.where(barclamp: @bc_name, name: proposal).first
          end
        end
      else
        format.json do
          render json: { error: message }, status: code
        end
        format.html do
          flash[:alert] = message

          redirect_to(
            root_url
          )
        end
      end
    end
  end

  add_help(:proposal_template, [], [:get])
  api :GET, "/crowbar/:barclamp/1.0/proposals/template",
    "Returns the content of a proposal template"
  header "Accept", "application/json", required: true
  param :barclamp, String, desc: "Name of the barclamp", required: true
  example '
  {
    "id": "template-dns",
    "description": "Manages the DNS subsystem for the cluster",
    "attributes": {
      "dns": {
        "domain": "pod.your.cloud.org",
        "forwarders": [],
        "allow_transfer": [],
        "nameservers": [],
        "records": {},
        "auto_assign_server": true
      }
    },
    "deployment": {
      "dns": {
        "crowbar-revision": 0,
        "crowbar-applied": false,
        "schema-revision": 100,
        "element_states": {
          "dns-server": [
            "readying",
            "ready",
            "applying"
          ],
          "dns-client": [
            "readying",
            "ready",
            "applying"
          ]
        },
        "elements": {},
        "element_order": [
          [
            "dns-server"
          ],
          [
            "dns-client"
          ]
        ],
        "element_run_list_order": {
          "dns-server": 30,
          "dns-client": 31
        },
        "config": {
          "environment": "dns-base-config",
          "mode": "full",
          "transitions": true,
          "transition_list": [
            "installed",
            "readying"
          ]
        }
      }
    }
  }
  '
  error 404, "Proposal template of barclamp not found"
  def proposal_template
    code, message = @service_object.proposal_template

    raise Crowbar::Error::NotFound if code == 404
    respond_to do |format|
      case code
      when 200
        format.json do
          render json: message
        end
      else
        format.json do
          render json: { error: message }, status: code
        end
      end
    end
  end

  add_help(:proposal_show, [:id], [:get])
  api :GET, "/crowbar/:barclamp/1.0/proposals/:id",
    "Returns the details of a specific proposal"
  header "Accept", "application/json", required: true
  param_group :proposal
  example '
  {
    "id": "dns-default",
    "description": "Manages the DNS subsystem for the cluster",
    "attributes": {
      "dns": {
        "domain": "cloud.crowbar.com",
        "forwarders": [
          "192.168.124.1"
        ],
        "allow_transfer": [],
        "nameservers": [],
        "records": {
          "multi-dns": {
            "ips": [
              "10.11.12.13"
            ]
          }
        },
        "auto_assign_server": true
      }
    },
    "deployment": {
      "dns": {
        "crowbar-revision": 3,
        "crowbar-applied": true,
        "schema-revision": 100,
        "element_states": {
          "dns-server": [
            "readying",
            "ready",
            "applying"
          ],
          "dns-client": [
            "readying",
            "ready",
            "applying"
          ]
        },
        "elements": {
          "dns-server": [
            "crowbar.crowbar.com"
          ],
          "dns-client": [
            "d52-54-77-77-77-01.crowbar.com",
            "d52-54-77-77-77-02.crowbar.com"
          ]
        },
        "element_order": [
          [
            "dns-server"
          ],
          [
            "dns-client"
          ]
        ],
        "element_run_list_order": {
          "dns-server": 30,
          "dns-client": 31
        },
        "config": {
          "environment": "dns-config-default",
          "mode": "full",
          "transitions": true,
          "transition_list": [
            "installed",
            "readying"
          ]
        },
        "crowbar-committing": true,
        "crowbar-status": "success",
        "crowbar-failed": ""
      }
    }
  }
  '
  error 404, "Proposal of barclamp not found"
  def proposal_show
    code, message = @service_object.proposal_show(
      params[:id]
    )

    raise Crowbar::Error::NotFound if code == 404
    respond_to do |format|
      case code
      when 200
        format.json do
          render json: message.raw_data
        end
        format.html do
          @proposal = message

          @active = begin
            RoleObject.active(
              params[:controller],
              params[:id]
            ).length > 0
          rescue
            false
          end

          flash.now[:alert] = @proposal.fail_reason if @proposal.failed?
        end
      else
        format.json do
          render json: { error: message }, status: code
        end
        format.html do
          flash[:alert] = message

          redirect_to(
            root_url
          )
        end
      end
    end
  end

  add_help(:proposal_delete, [:id], [:delete])
  api :DELETE, "/crowbar/:barclamp/1.0/proposals/:id",
    "Remove a specific proposal"
  header "Accept", "application/json", required: true
  param_group :proposal
  error 404, "Proposal of barclamp not found"
  def proposal_delete
    code, message = @service_object.proposal_delete(
      params[:id]
    )

    raise Crowbar::Error::NotFound if code == 404
    respond_to do |format|
      case code
      when 200
        format.html do
          redirect_to(
            barclamp_modules_path(
              id: params[:controller]
            )
          )
        end
        format.json do
          head :ok
        end
      else
        format.html do
          flash[:alert] = message

          redirect_to(
            barclamp_modules_path(
              id: params[:controller]
            )
          )
        end
        format.json do
          render json: { error: message }, status: code
        end
      end
    end
  end

  add_help(:proposal_commit, [:id], [:post])
  api :POST, "/crowbar/:barclamp/1.0/proposals/commit/:id",
    "Commit a specific proposal to apply it"
  header "Accept", "application/json", required: true
  param_group :proposal
  error 400, "Invalid proposal"
  error 402, "Proposal already committing"
  error 404, "Proposal of barclamp not found"
  error 500, "Failed to commit proposal, details in the response"
  def proposal_commit
    code, message = @service_object.proposal_commit(
      params[:id]
    )

    raise Crowbar::Error::NotFound if code == 404
    respond_to do |format|
      case code
      when 200
        format.html do
          redirect_to(
            show_proposal_path(
              controller: params[:controller],
              id: params[:id]
            )
          )
        end
        format.json do
          head :ok
        end
      when 202
        format.html do
          flash[:warning] = message

          redirect_to(
            show_proposal_path(
              controller: params[:controller],
              id: params[:id]
            )
          )
        end
        format.json do
          head :accepted
        end
      else
        format.html do
          flash[:alert] = message

          redirect_to(
            show_proposal_path(
              controller: params[:controller],
              id: params[:id]
            )
          )
        end
        format.json do
          render json: { error: message }, status: code
        end
      end
    end
  end

  add_help(:proposal_reset, [:id], [:post])
  api :POST, "/crowbar/:barclamp/1.0/proposals/reset/:id",
    "Reset a specific proposal status"
  header "Accept", "application/json", required: true
  param_group :proposal
  error 404, "Proposal of barclamp not found"
  error 422, "Failed to reset proposal, details in the response"
  def proposal_reset
    code, message = @service_object.reset_proposal(
      params[:id]
    )

    raise Crowbar::Error::NotFound if code == 404
    respond_to do |format|
      case code
      when 200
        format.html do
          redirect_to(
            show_proposal_path(
              controller: params[:controller],
              id: params[:id]
            )
          )
        end
        format.json do
          head :ok
        end
      else
        format.html do
          flash[:alert] = message

          redirect_to(
            show_proposal_path(
              controller: params[:controller],
              id: params[:id]
            )
          )
        end
        format.json do
          render json: { error: message }, status: code
        end
      end
    end
  end

  add_help(:proposal_dequeue, [:id], [:delete])
  api :DELETE, "/crowbar/:barclamp/1.0/proposals/dequeue/:id",
    "Reset a specific proposal from the queue"
  header "Accept", "application/json", required: true
  param_group :proposal
  error 400, "Failed to dequeue proposal, details in the response"
  def proposal_dequeue
    code, message = @service_object.dequeue_proposal(
      params[:id]
    )

    raise Crowbar::Error::NotFound if code == 404
    respond_to do |format|
      case code
      when 200
        format.html do
          redirect_to(
            show_proposal_path(
              controller: params[:controller],
              id: params[:id]
            )
          )
        end
        format.json do
          head :ok
        end
      else
        format.html do
          flash[:alert] = message

          redirect_to(
            show_proposal_path(
              controller: params[:controller],
              id: params[:id]
            )
          )
        end
        format.json do
          render json: { error: message }, status: code
        end
      end
    end
  end

  add_help(:proposal_update, [:id], [:post])
  api :POST, "/crowbar/:barclamp/1.0/proposals/:id",
    "Update a specific proposal"
  header "Accept", "application/json", required: true
  param_group :proposal
  error 400, "Invalid proposal"
  error "any other http server exception", "Unknown error"
  def proposal_update
    if params[:submit].nil?
      #
      # This is RESTFul path
      #

      code, message = @service_object.proposal_edit(
        params.slice(
          :id,
          :description,
          :attributes,
          :deployment,
          "crowbar-deep-merge-template"
        )
      )
      # See FIXME in ServiceObject.apply_role
      message = "" if code == 202

      respond_to do |format|
        case code
        when 200
          format.html do
            redirect_to(
              show_proposal_path(
                controller: params[:controller],
                id: params[:id]
              )
            )
          end
          format.json do
            render json: message
          end
        else
          format.html do
            flash[:alert] = message

            redirect_to(
              show_proposal_path(
                controller: params[:controller],
                id: params[:id]
              )
            )
          end
          format.json do
            render json: { error: message }, status: code
          end
        end
      end
    else
      #
      # This is the UI path
      #

      if params[:submit] == t("barclamp.proposal_show.save_proposal")
        @proposal = Proposal.where(barclamp: params[:barclamp], name: params[:id] || params[:name]).first

        begin
          @proposal["attributes"][params[:barclamp]] = JSON.parse(params[:proposal_attributes])
          @proposal["deployment"][params[:barclamp]] = JSON.parse(params[:proposal_deployment])
          @service_object.save_proposal!(@proposal)
          flash[:notice] = t("barclamp.proposal_show.save_proposal_success")
        rescue StandardError => e
          flash_and_log_exception(e)
        end
      elsif params[:submit] == t("barclamp.proposal_show.commit_proposal")
        @proposal = Proposal.where(barclamp: params[:barclamp], name: params[:id] || params[:name]).first

        begin
          @proposal["attributes"][params[:barclamp]] = JSON.parse(params[:proposal_attributes])
          @proposal["deployment"][params[:barclamp]] = JSON.parse(params[:proposal_deployment])
          @service_object.save_proposal!(@proposal)
          # validation already happened on save
          answer = @service_object.proposal_commit(params[:name],
                                                   validate: false,
                                                   validate_after_save: false)
          flash[:alert] = answer[1] if answer[0] >= 400
          flash[:notice] = answer[1] if answer[0] >= 300 and answer[0] < 400
          flash[:notice] = t("barclamp.proposal_show.commit_proposal_success") if answer[0] == 200
          if answer[0] == 202
            missing_nodes = answer[1].map { |node_dns| Node.find_by_name(node_dns) }

            unready_nodes = missing_nodes.select { |n| n.state != "ready" }.map(&:alias)
            unallocated_nodes = missing_nodes.reject(&:allocated?).map(&:alias)

            unless unready_nodes.empty?
              flash[:notice] = t(
                "barclamp.proposal_show.commit_proposal_queued",
                nodes: (unready_nodes - unallocated_nodes).join(", ")
              )
            end
            unless unallocated_nodes.empty?
              flash[:alert] = t(
                "barclamp.proposal_show.commit_proposal_queued_unallocated",
                nodes: unallocated_nodes.join(", ")
              )
            end
            if unready_nodes.empty? && unallocated_nodes.empty?
              # find out which proposals were not applied yet
              deps = @service_object.proposal_dependencies(
                ServiceObject.proposal_to_role(@proposal, params[:barclamp])
              )
              missing_barclamps = deps.map do |dep|
                prop = Proposal.where(barclamp: dep["barclamp"], name: dep["inst"]).first
                queued   = prop["deployment"][dep["barclamp"]]["crowbar-queued"] rescue false
                deployed = (prop["deployment"][dep["barclamp"]]["crowbar-status"] == "success") rescue false
                dep["barclamp"] if queued || !deployed
              end.compact
              flash[:notice] = t(
                "barclamp.proposal_show.commit_proposal_queued_dependency",
                barclamps: missing_barclamps.join(", ")
              )
            end
          end
        rescue StandardError => e
          flash_and_log_exception(e)
        end
      elsif params[:submit] == t("barclamp.proposal_show.delete_proposal")
        begin
          answer = @service_object.proposal_delete(params[:name])
          set_flash(answer, "barclamp.proposal_show.delete_proposal_%s")
        rescue StandardError => e
          flash_and_log_exception(e)
        end
        redirect_to barclamp_modules_path(id: (params[:barclamp] || ""))
        return
      elsif params[:submit] == t("barclamp.proposal_show.destroy_active")
        begin
          answer = @service_object.destroy_active(params[:name])
          set_flash(answer, "barclamp.proposal_show.destroy_active_%s")
        rescue StandardError => e
          flash_and_log_exception(e)
        end
      elsif params[:submit] == t("barclamp.proposal_show.dequeue_proposal")
        begin
          answer = @service_object.dequeue_proposal(params[:name])
          set_flash(answer, "barclamp.proposal_show.dequeue_proposal_%s")
        rescue StandardError => e
          flash_and_log_exception(e)
        end
      else
        Rails.logger.warn "Invalid action #{params[:submit]} for #{params[:id]}"
        flash[:alert] = "Invalid action #{params[:submit]}"
      end

      if params[:origin] && params[:origin] == "deployment_queue"
        redirect_to deployment_queue_index_path
      else
        redirect_params = {
          controller: params[:barclamp],
          id: params[:name]
        }

        redirect_params[:dep_raw] = true if view_context.show_raw_deployment?
        redirect_params[:attr_raw] = true if view_context.show_raw_attributes?

        redirect_to show_proposal_path(redirect_params)
      end
    end
  end

  add_help(:proposal_create, [:name], [:put])
  api :PUT, "/crowbar/:barclamp/1.0/proposals",
    "Create a new specific proposal"
  header "Accept", "application/json", required: true
  param :barclamp, String, desc: "Name of the barclamp", required: true
  error 400, "Invalid proposal, details in response"
  error 403, "Illegal proposal name"
  error 412, "Failed to create proposal, details in response"
  def proposal_create
    params[:id] = params[:id] || params[:name]

    begin
      code, message = @service_object.proposal_create(
        params.slice(
          :id,
          :description,
          :attributes,
          :deployment,
          "crowbar-deep-merge-template"
        )
      )
    rescue RuntimeError => e
      code = 412
      message = e.to_s
    end

    respond_to do |format|
      case code
      when 200
        format.json do
          render json: message
        end
        format.html do
          redirect_to(
            show_proposal_path(
              controller: params[:controller],
              id: params[:id]
            )
          )
        end
      else
        format.json do
          render json: { error: message }, status: code
        end
        format.html do
          flash[:alert] = message

          redirect_to(
            barclamp_modules_path(
              id: params[:controller]
            )
          )
        end
      end
    end
  end

  #
  # Currently, A UI ONLY METHOD
  #
  add_help(:proposal_status, [:id, :barclamp, :name], [:get])
  def proposal_status
    proposals = {}
    i18n = {}

    begin
      active = RoleObject.active(
        params[:barclamp],
        params[:name]
      )

      result = if params[:id].nil?
        Proposal.all
      else
        [
          Proposal.where(
            barclamp: params[:barclamp],
            name: params[:name]
          ).first
        ].compact
      end

      result.each do |prop|
        prop_id = "#{prop.barclamp}_#{prop.name}"
        proposals[prop_id] = prop.real_status(active)

        i18n[prop_id] = {
          proposal: prop.name.humanize,
          status: t(
            "proposal.status.#{proposals[prop_id]}",
            default: proposals[prop_id]
          )
        }
      end

      render inline: {
        proposals: proposals,
        i18n: i18n,
        count: proposals.length
      }.to_json, cache: false
    rescue StandardError => e
      count = (e.class.to_s == "Errno::ECONNREFUSED" ? -2 : -1)
      lines = ["Failed to iterate over proposal list due to '#{e.message}'"] + e.backtrace
      Rails.logger.fatal(lines.join("\n"))

      render inline: {
        proposals: proposals,
        count: count,
        error: e.message
      }.to_json, cache: false
    end
  end

  add_help(:nodes, [], [:get])
  def nodes
    #Empty method to override if your barclamp has a "nodes" view.
  end

  private
  def set_flash(answer, common, success="success", failure="failure")
    if answer[0] == 200
      flash[:notice] = t(common % success)
    else
      flash[:alert] = t(common % failure)
      flash[:alert] += ": " + answer[1].to_s unless answer[1].to_s.empty?
    end
  end

  #
  # This is a role output function
  # Can take either a RoleObject or a Role.
  #
  def role_to_proposal(role, bc_name)
    proposal = {}
    proposal["id"] = role.name.gsub("#{bc_name}-config-", "#{bc_name}-")
    proposal["description"] = role.description
    proposal["attributes"] = role.default_attributes
    proposal["deployment"] = role.override_attributes
    proposal
  end

  protected

  def get_proposals_from_barclamps(barclamps)
    modules = {}
    active = RoleObject.active
    barclamps.each do |name, details|
      modules[name] = {
        description: details["description"] || t("not_set"), order: details["order"],
        proposals: {},
        expand: false,
        members: (details["members"].nil? ? 0 : details["members"].length)
      }

      bc_service = ServiceObject.get_service(name)
      modules[name][:allow_multiple_proposals] = bc_service.allow_multiple_proposals?
      suggested_proposal_name = bc_service.suggested_proposal_name

      Proposal.where(barclamp: name).each do |prop|
        @count += 1 unless @count < 0 # allows caller to skip incrementing by initializing to -1
        modules[name][:proposals][prop.name] = {
          id: prop.id,
          description: prop.description,
          status: prop.real_status(active),
          active: prop.active_status?(active)
        }
        if prop.status == "failed"
          modules[name][:proposals][prop.name][:message] = prop.fail_reason
          modules[name][:expand] = true
        end
      end

      # find a free proposal name for what would be the next proposal
      modules[name][:suggested_proposal_name] = suggested_proposal_name
      (1..20).each do |x|
        possible_name = "#{suggested_proposal_name}_#{x}"
        next if active.include?("#{name}_#{possible_name}")
        next if modules[name][:proposals].keys.include?(possible_name)
        modules[name][:suggested_proposal_name] = possible_name
        break
      end if modules[name][:allow_multiple_proposals]
    end
    modules
  end

  def initialize_service
    @service_object = ServiceObject.new
  end
end