crowbar/crowbar-core

View on GitHub
crowbar_framework/spec/models/api/crowbar_spec.rb

Summary

Maintainability
F
3 days
Test Coverage
require "spec_helper"

describe Api::Crowbar do
  let(:pid) { rand(20000..30000) }
  let(:admin_node) { NodeObject.find_node_by_name("admin") }
  let(:cinder_proposal) do
    Proposal.where(barclamp: "cinder", name: "default").create(barclamp: "cinder", name: "default")
  end

  let!(:crowbar_upgrade_status) do
    JSON.parse(
      File.read(
        "spec/fixtures/crowbar_upgrade_status.json"
      )
    )
  end
  let!(:crowbar_object) do
    JSON.parse(
      File.read(
        "spec/fixtures/crowbar_object.json"
      )
    )
  end
  let!(:node) { Node.find_by_name("testing.crowbar.com") }
  let!(:drbd_node) { Node.find_by_name("drbd.crowbar.com") }
  let!(:crowbar_role) { RoleObject.find_role_by_name("crowbar") }
  let!(:cinder_controller_role) { RoleObject.find_role_by_name("cinder-controller") }

  before(:each) do
    allow_any_instance_of(Kernel).to(
      receive(:spawn).
        and_return(pid)
    )
    allow(Process).to(
      receive(:detach).
        with(pid).
        and_return(pid)
    )
  end

  context "with a successful status" do
    it "checks the status" do
      expect(subject.class).to respond_to(:status)
      expect(subject.class.status).to be_a(Hash)
      expect(subject.class.status.stringify_keys).to eq(crowbar_object)
    end
  end

  context "with a successful upgrade" do
    it "shows the status of the upgrade" do
      expect(subject.class).to respond_to(:upgrade)
      expect(subject.class.upgrade.deep_stringify_keys).to eq(crowbar_upgrade_status)
    end

    it "triggers the upgrade" do
      allow(Api::Crowbar).to(
        receive_message_chain(:upgrade_script_path, :exist?).
        and_return(true)
      )
      allow_any_instance_of(Crowbar::UpgradeStatus).to receive(
        :start_step
      ).with(:admin).and_return(true)
      allow_any_instance_of(Crowbar::UpgradeStatus).to receive(
        :end_step
      ).and_return(true)

      expect(subject.class.upgrade!).to eq(
        status: :ok,
        message: ""
      )
    end
  end

  context "with a failed upgrade" do
    it "cannot find the upgrade script" do
      allow(Api::Crowbar).to(
        receive_message_chain(:upgrade_script_path, :exist?).
        and_return(false)
      )

      expect(subject.class.upgrade![:status]).to eq(:unprocessable_entity)
    end

    it "is already upgrading" do
      allow(Api::Crowbar).to(
        receive(:upgrading?).and_return(true)
      )

      expect(subject.class.upgrade![:status]).to eq(:unprocessable_entity)
    end
  end

  context "with addons enabled" do
    it "lists the enabled addons" do
      ["ha"].each do |addon|
        allow(Api::Crowbar).to(
          receive(:addon_installed?).with(addon).
          and_return(true)
        )
        allow(Api::Crowbar).to(
          receive(:addon_deployed?).with(addon).
          and_return(true)
        )
        allow(Api::Node).to(
          receive(:repocheck).with(addon: addon).and_return(
            addon => { "available" => true }
          )
        )
      end

      expect(subject.class.addons).to eq(["ha"])
    end
  end

  context "with no addons enabled" do
    it "lists no addons" do
      expect(subject.class.addons).to eq([])
    end
  end
  context "with cloud healthy" do
    it "succeeds to check cloud health" do
      allow(NodeObject).to(
        receive(:find_all_nodes).
        and_return([NodeObject.find_node_by_name("testing.crowbar.com")])
      )
      allow_any_instance_of(NodeObject).to(receive(:ready?).and_return(true))

      expect(subject.class.health_check).to be_empty
    end
  end

  context "with cloud not healthy" do
    it "finds a node that is not ready" do
      allow(NodeObject).to(
        receive(:find_all_nodes).and_return(
          [NodeObject.find_node_by_name("testing.crowbar.com")]
        )
      )
      allow_any_instance_of(NodeObject).to(receive(:ready?).and_return(false))

      expect(subject.class.health_check).to have_key(:nodes_not_ready)
    end

    it "finds a failed and active proposal" do
      allow(NodeObject).to(
        receive(:find_all_nodes).
        and_return([NodeObject.find_node_by_name("testing.crowbar.com")])
      )
      allow_any_instance_of(NodeObject).to(receive(:ready?).and_return(true))

      allow(Proposal).to(
        receive(:all).and_return([Proposal.new(barclamp: "crowbar")])
      )
      allow_any_instance_of(Proposal).to(receive(:active?).and_return(true))
      allow_any_instance_of(Proposal).to(receive(:failed?).and_return(true))

      expect(subject.class.health_check).to eq(failed_proposals: ["Crowbar"])
    end
  end

  context "with ceph cluster deployed by crowbar" do
    it "fails when ceph roles are found" do
      allow(Node).to(
        receive(:find).with("roles:ceph-* AND ceph_config_environment:*").
        and_return([Node.find_by_name("testing.crowbar.com")])
      )
      expect(subject.class.ceph_status).to eq(
        crowbar_ceph_nodes: true
      )
    end
  end

  context "with HA deployed" do
    it "succeeds to confirm that HA is deployed with correct cinder backend" do
      cinder_proposal.raw_data["attributes"] = {
        "cinder" => { "volumes" => [{ "backend_driver" => "rbd" }] }
      }
      allow(Proposal).to(receive(:where).and_return([]))
      allow(Proposal).to(receive(:where).with(barclamp: "cinder").and_return([cinder_proposal]))

      allow(Node).to(receive(:find).with("roles:nova-compute-kvm").and_return([node]))
      allow_any_instance_of(Node).to(
        receive(:roles).and_return(["nova-compute-kvm", "swift-storage"])
      )

      expect(subject.class.ha_config_check).to eq({})
    end

    it "fails when finding out cinder is using raw backend" do
      cinder_proposal.raw_data["attributes"] = {
        "cinder" => { "volumes" => [{ "backend_driver" => "raw" }] }
      }
      allow(Proposal).to(receive(:where).and_return([]))
      allow(Proposal).to(receive(:where).with(barclamp: "cinder").and_return([cinder_proposal]))

      expect(subject.class.ha_config_check).to eq(cinder_wrong_backend: true)
    end

    it "fails when controller role is deployed to compute node" do
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-xen").and_return([]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-kvm").and_return([node]))
      allow_any_instance_of(NodeObject).to(
        receive(:roles).and_return(
          ["cinder-controller", "nova-compute-kvm", "neutron-server", "cinder-volume"]
        )
      )

      expect(subject.class.ha_config_check).to eq(
        role_conflicts: {
          "testing.crowbar.com" => ["cinder-controller", "neutron-server", "cinder-volume"]
        }
      )
    end

    def barclamp_config_helper(attributes, deployment)
      deployment.each do |bc, bc_data|
        allow(Proposal).to(
          receive(:where).with(barclamp: bc).and_return(
            [{
              "attributes" => attributes[bc],
              "deployment" => { bc => { "elements" => bc_data } }
            }]
          )
        )
      end
    end

    it "succeeds when there are two clusters and one is dedicated to neutron" do
      allow(Api::Crowbar).to(receive(:addon_installed?).and_return(true))
      allow(NodeObject).to(receive(:find).with(
        "pacemaker_founder:true AND pacemaker_config_environment:*"
      ).and_return([node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-xen").and_return([]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-kvm").and_return([]))

      barclamps_clusters = {
        "database" => { "database-server" => ["cluster1"] },
        "rabbitmq" => { "rabbitmq-server" => ["cluster1"] },
        "keystone" => { "keystone-server" => ["cluster1"] },
        "glance" => { "glance-server" => ["cluster1"] },
        "cinder" => { "cinder-controller" => ["cluster1"] },
        "neutron" => { "neutron-server" => ["cluster2"], "neutron-network" => ["cluster2"] },
        "nova" => { "nova-controller" => ["cluster1"] }
      }
      barclamps_attributes = {
        "cinder" => { "cinder" => { "volumes" => [] } }
      }
      barclamp_config_helper(barclamps_attributes, barclamps_clusters)

      allow(ServiceObject).to(receive(:is_cluster?).and_return(true))

      expect(subject.class.ha_config_check).to eq({})
    end

    it "succeeds when there are two clusters and one is dedicated to db" do
      allow(Api::Crowbar).to(receive(:addon_installed?).and_return(true))
      allow(NodeObject).to(receive(:find).with(
        "pacemaker_founder:true AND pacemaker_config_environment:*"
      ).and_return([node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-xen").and_return([]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-kvm").and_return([]))

      barclamps_clusters = {
        "database" => { "database-server" => ["cluster2"] },
        "rabbitmq" => { "rabbitmq-server" => ["cluster2"] },
        "keystone" => { "keystone-server" => ["cluster1"] },
        "glance" => { "glance-server" => ["cluster1"] },
        "cinder" => { "cinder-controller" => ["cluster1"] },
        "neutron" => { "neutron-server" => ["cluster1"], "neutron-network" => ["cluster1"] },
        "nova" => { "nova-controller" => ["cluster1"] }
      }
      barclamps_attributes = {
        "cinder" => { "cinder" => { "volumes" => [] } }
      }
      barclamp_config_helper(barclamps_attributes, barclamps_clusters)

      allow(ServiceObject).to(receive(:is_cluster?).and_return(true))

      expect(subject.class.ha_config_check).to eq({})
    end

    it "succeeds when there are three clusters and they are db+apis+network" do
      allow(Api::Crowbar).to(receive(:addon_installed?).and_return(true))
      allow(NodeObject).to(receive(:find).with(
        "pacemaker_founder:true AND pacemaker_config_environment:*"
      ).and_return([node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-xen").and_return([]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-kvm").and_return([]))

      barclamps_clusters = {
        "database" => { "database-server" => ["cluster1"] },
        "rabbitmq" => { "rabbitmq-server" => ["cluster1"] },
        "keystone" => { "keystone-server" => ["cluster2"] },
        "glance" => { "glance-server" => ["cluster2"] },
        "cinder" => { "cinder-controller" => ["cluster2"] },
        "neutron" => { "neutron-server" => ["cluster2"], "neutron-network" => ["cluster3"] },
        "nova" => { "nova-controller" => ["cluster2"] }
      }
      barclamps_attributes = {
        "cinder" => { "cinder" => { "volumes" => [] } }
      }
      barclamp_config_helper(barclamps_attributes, barclamps_clusters)

      allow(ServiceObject).to(receive(:is_cluster?).and_return(true))

      expect(subject.class.ha_config_check).to eq({})
    end

    it "fails when there are four clusters" do
      allow(Api::Crowbar).to(receive(:addon_installed?).and_return(true))
      allow(NodeObject).to(receive(:find).with(
        "pacemaker_founder:true AND pacemaker_config_environment:*"
      ).and_return([node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-xen").and_return([]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-kvm").and_return([]))

      barclamps_clusters = {
        "database" => { "database-server" => ["cluster1"] },
        "rabbitmq" => { "rabbitmq-server" => ["cluster1"] },
        "keystone" => { "keystone-server" => ["cluster2"] },
        "glance" => { "glance-server" => ["cluster2"] },
        "cinder" => { "cinder-controller" => ["cluster2"] },
        "neutron" => { "neutron-server" => ["cluster2"], "neutron-network" => ["cluster3"] },
        "nova" => { "nova-controller" => ["cluster4"] }
      }
      barclamps_attributes = {
        "cinder" => { "cinder" => { "volumes" => [] } }
      }
      barclamp_config_helper(barclamps_attributes, barclamps_clusters)

      allow(ServiceObject).to(receive(:is_cluster?).and_return(true))

      expect(subject.class.ha_config_check).to have_key(:unsupported_cluster_setup)
    end

    it "fails when there are three clusters and db/api/network roles are mixed" do
      allow(Api::Crowbar).to(receive(:addon_installed?).and_return(true))
      allow(NodeObject).to(receive(:find).with(
        "pacemaker_founder:true AND pacemaker_config_environment:*"
      ).and_return([node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-xen").and_return([]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-kvm").and_return([]))

      barclamps_clusters = {
        "database" => { "database-server" => ["cluster1"] },
        "rabbitmq" => { "rabbitmq-server" => ["cluster2"] },
        "keystone" => { "keystone-server" => ["cluster3"] },
        "glance" => { "glance-server" => ["cluster1"] },
        "cinder" => { "cinder-controller" => ["cluster2"] },
        "neutron" => { "neutron-server" => ["cluster3"], "neutron-network" => ["cluster1"] },
        "nova" => { "nova-controller" => ["cluster2"] }
      }
      barclamps_attributes = {
        "cinder" => { "cinder" => { "volumes" => [] } }
      }
      barclamp_config_helper(barclamps_attributes, barclamps_clusters)

      allow(ServiceObject).to(receive(:is_cluster?).and_return(true))

      expect(subject.class.ha_config_check).to have_key(:unsupported_cluster_setup)
    end

    it "fails when there are two clusters and roles assignment does not match supported patterns" do
      allow(Api::Crowbar).to(receive(:addon_installed?).and_return(true))
      allow(NodeObject).to(receive(:find).with(
        "pacemaker_founder:true AND pacemaker_config_environment:*"
      ).and_return([node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-xen").and_return([]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-kvm").and_return([]))

      barclamps_clusters = {
        "database" => { "database-server" => ["cluster1"] },
        "rabbitmq" => { "rabbitmq-server" => ["cluster2"] },
        "keystone" => { "keystone-server" => ["cluster1"] },
        "glance" => { "glance-server" => ["cluster2"] },
        "cinder" => { "cinder-controller" => ["cluster1"] },
        "neutron" => { "neutron-server" => ["cluster2"], "neutron-network" => ["cluster1"] },
        "nova" => { "nova-controller" => ["cluster2"] }
      }
      barclamps_attributes = {
        "cinder" => { "cinder" => { "volumes" => [] } }
      }
      barclamp_config_helper(barclamps_attributes, barclamps_clusters)

      allow(ServiceObject).to(receive(:is_cluster?).and_return(true))

      expect(subject.class.ha_config_check).to have_key(:unsupported_cluster_setup)
    end
  end

  context "with correct barclamps deployment" do
    it "passes with nice compute nodes" do
      allow(NodeObject).to(receive(:find).with("roles:database-server").and_return([node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-*").and_return([node]))
      allow_any_instance_of(NodeObject).to(
        receive(:roles).and_return(
          ["nova-compute-kvm", "cinder-volume", "swift-storage"]
        )
      )
      allow_any_instance_of(RoleObject).to(receive(:proposal?).and_return(false))

      expect(subject.class.deployment_check).to be_empty
    end

    it "passes with remote compute node" do
      allow(NodeObject).to(receive(:find).with("roles:database-server").and_return([node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-*").and_return([node]))
      allow_any_instance_of(NodeObject).to(
        receive(:roles).and_return(
          ["nova-compute-kvm", "pacemaker-remote"]
        )
      )
      allow_any_instance_of(RoleObject).to(receive(:proposal?).and_return(false))

      expect(subject.class.deployment_check).to be_empty
    end

    it "passes with compute node together with nova-controller " do
      allow(NodeObject).to(receive(:find).with("roles:database-server").and_return([node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-*").and_return([node]))
      allow_any_instance_of(NodeObject).to(
        receive(:roles).and_return(
          ["nova-compute-kvm", "cinder-controller", "nova-controller"]
        )
      )
      allow_any_instance_of(RoleObject).to(receive(:proposal?).and_return(false))

      expect(subject.class.deployment_check).to be_empty
    end
  end

  context "with broken barclamps deployment" do
    it "fails when cinder-controller is on compute node" do
      allow(NodeObject).to(receive(:find).with("roles:database-server").and_return([node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-*").and_return([node]))
      allow_any_instance_of(NodeObject).to(
        receive(:roles).and_return(
          ["nova-compute-kvm", "cinder-controller"]
        )
      )
      allow(RoleObject).to(receive(:find_role_by_name).with(
        "cinder-controller"
      ).and_return(cinder_controller_role))
      allow(BarclampCatalog).to(receive(:category).with(
        "cinder"
      ).and_return("OpenStack"))
      allow(BarclampCatalog).to(receive(:run_order).with("nova").and_return(10))
      allow(BarclampCatalog).to(receive(:run_order).with("cinder").and_return(5))

      allow_any_instance_of(RoleObject).to(receive(:proposal?).and_return(false))

      expect(subject.class.deployment_check).to eq(
        controller_roles: { node: "testing.crowbar.com", roles: ["cinder-controller"] }
      )
    end
    it "fails when postgresql is deployed as OpenStack DB" do
      allow(NodeObject).to(receive(:find).with("roles:database-server").and_return([drbd_node]))
      allow(NodeObject).to(receive(:find).with("roles:nova-compute-*").and_return([node]))
      allow_any_instance_of(NodeObject).to(
        receive(:roles).and_return(
          ["nova-compute-kvm", "cinder-volume", "swift-storage"]
        )
      )
      allow_any_instance_of(RoleObject).to(receive(:proposal?).and_return(false))

      expect(subject.class.deployment_check).to eq(
        wrong_sql_engine: true
      )
    end

  end

  context "with enough compute resources" do
    it "succeeds to find enough KVM compute nodes" do
      allow(Node).to(
        receive(:find).with("roles:nova-compute-kvm").
        and_return([node, node])
      )
      allow(Node).to(receive(:find).with(
        "roles:nova-compute-* AND NOT roles:nova-compute-kvm"
      ).and_return([]))
      allow(Node).to(
        receive(:find).with("roles:nova-controller").
        and_return([node])
      )
      expect(subject.class.compute_status).to be_empty
    end
  end

  context "with not enough compute resources" do
    it "finds there is only one KVM compute node and fails" do
      allow(Node).to(
        receive(:find).with("roles:nova-compute-kvm").
        and_return([node])
      )
      allow(Node).to(receive(:find).with(
        "roles:nova-compute-* AND NOT roles:nova-compute-kvm"
      ).and_return([]))
      allow(Node).to(
        receive(:find).with("roles:nova-controller").and_return([node])
      )
      expect(subject.class.compute_status).to eq(
        no_resources:
        "Found only one KVM compute node; non-disruptive upgrade is not possible"
      )
    end
  end

  context "with various compute node types" do
    it "finds there is non KVM compute node and fails" do
      allow(Node).to(
        receive(:find).with("roles:nova-compute-kvm").
        and_return([node, node])
      )
      allow(Node).to(receive(:find).with(
        "roles:nova-compute-* AND NOT roles:nova-compute-kvm"
      ).and_return([node]))
      allow(Node).to(
        receive(:find).with("roles:nova-controller").and_return([node])
      )
      expect(subject.class.compute_status).to eq(
        non_kvm_computes: ["testing.crowbar.com"]
      )
    end
  end

  context "with no compute resources" do
    it "finds there is no compute node at all" do
      allow(Node).to(
        receive(:find).with("roles:nova-compute-kvm").
        and_return([])
      )
      allow(Node).to(receive(:find).with(
        "roles:nova-compute-* AND NOT roles:nova-compute-kvm"
      ).and_return([]))
      allow(Node).to(
        receive(:find).with("roles:nova-controller").and_return([node])
      )
      expect(subject.class.compute_status).to be_empty
    end
  end
end