#
# Author:: Cary Penniman (<cary@rightscale.com>)
# License:: Apache License, Version 2.0
#
# 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 "resolv"
require "spec_helper"

describe Ohai::System, "plugin rackspace" do
  let(:plugin) { get_plugin("rackspace") }

  before do
    allow(Resolv).to receive(:getname).and_return("1.2.3.4")

    plugin[:hostname] = "katie"

    plugin[:network] = {
      interfaces: {
        eth0: {
          "addresses" => {
            "1.2.3.4" => {
              "broadcast" => "67.23.20.255",
              "netmask" => "255.255.255.0",
              "family" => "inet",
            },
            "2a00:1a48:7805:111:e875:efaf:ff08:75" => {
              "family" => "inet6",
              "prefixlen" => "64",
              "scope" => "Global",
            },
            "fe80::4240:95ff:fe47:6eed" => {
              "scope" => "Link",
              "prefixlen" => "64",
              "family" => "inet6",
            },
            "40:40:95:47:6E:ED" => {
              "family" => "lladdr",
            },
          },
        },
      },
    }

    plugin[:network][:interfaces][:eth1] = {
      addresses: {
        "fe80::4240:f5ff:feab:2836" => {
          "scope" => "Link",
          "prefixlen" => "64",
          "family" => "inet6",
        },
        "5.6.7.8" => {
          "broadcast" => "10.176.191.255",
          "netmask" => "255.255.224.0",
          "family" => "inet",
        },
        "40:40:F5:AB:28:36" => {
          "family" => "lladdr",
        },
      },
    }

    # In olden days we could detect rackspace by a -rscloud suffix on the kernel
    # This is here to make #has_rackspace_kernel? fail until we remove that check
    plugin[:kernel] = { release: "1.2.13-not-rackspace" }

    # We need a generic stub here for the later stubs with arguments to work
    # Because, magic.
    allow(plugin).to receive(:shell_out).and_return(mock_shell_out(1, "", ""))
  end

  shared_examples_for "!rackspace" do
    it "does not create rackspace attribute" do
      plugin.run
      expect(plugin[:rackspace]).to be_nil
    end
  end

  shared_examples_for "rackspace" do
    it "has rackspace attribute" do
      plugin.run
      expect(plugin[:rackspace]).not_to be_nil
    end

    it "has expected rackspace ip/hostname attributes" do
      plugin.run
      expect(plugin[:rackspace][:public_ip]).not_to be_nil
      expect(plugin[:rackspace][:private_ip]).not_to be_nil
      expect(plugin[:rackspace][:public_ipv4]).not_to be_nil
      expect(plugin[:rackspace][:local_ipv4]).not_to be_nil
      expect(plugin[:rackspace][:public_ipv6]).not_to be_nil
      expect(plugin[:rackspace][:local_ipv6]).to be_nil
      expect(plugin[:rackspace][:local_hostname]).not_to be_nil
      expect(plugin[:rackspace][:public_hostname]).not_to be_nil
    end

    it "resolves hostname if reverse dns is set" do
      allow(Resolv).to receive(:getname).and_return("1234.resolved.com")
      plugin.run
      expect(plugin[:rackspace][:public_hostname]).to eq("1234.resolved.com")
    end

    [Resolv::ResolvError, Resolv::ResolvTimeout].each do |exception|
      it "returns ip address when reverse dns returns exception: #{exception}" do
        allow(Resolv).to receive(:getname).and_raise(exception)
        plugin.run
        expect(plugin[:rackspace][:public_hostname]).to eq("1.2.3.4")
      end
    end

    it "has correct values for all attributes" do
      plugin.run
      expect(plugin[:rackspace][:public_ip]).to eq("1.2.3.4")
      expect(plugin[:rackspace][:private_ip]).to eq("5.6.7.8")
      expect(plugin[:rackspace][:public_ipv4]).to eq("1.2.3.4")
      expect(plugin[:rackspace][:local_ipv4]).to eq("5.6.7.8")
      expect(plugin[:rackspace][:public_ipv6]).to eq("2a00:1a48:7805:111:e875:efaf:ff08:75")
      expect(plugin[:rackspace][:local_hostname]).to eq("katie")
      expect(plugin[:rackspace][:public_hostname]).to eq("1.2.3.4")
    end

    it "captures region information" do
      provider_data = <<~OUT
        provider = "Rackspace"
        service_type = "cloudServers"
        server_id = "21301000"
        created_at = "2012-12-06T22:08:16Z"
        region = "dfw"
      OUT
      allow(plugin).to receive(:shell_out).with("xenstore-ls vm-data/provider_data").and_return(mock_shell_out(0, provider_data, ""))
      plugin.run
      expect(plugin[:rackspace][:region]).to eq("dfw")
    end

    it "logs a debug message when region info cannot be collected" do
      expect(plugin)
        .to receive(:shell_out)
        .with("xenstore-ls vm-data/provider_data")
        .and_raise(Ohai::Exceptions::Exec)

      expect(plugin.logger)
        .to receive(:trace)
        .with("Plugin Rackspace: Unable to find xenstore-ls, cannot capture " \
             "region information for Rackspace cloud")

      plugin.run

      expect(plugin[:rackspace]).not_to have_key(:region)
    end

    it "captures instance ID information" do
      provider_data = "instance-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
      allow(plugin).to receive(:shell_out).with("xenstore-read name").and_return(mock_shell_out(0, provider_data, ""))
      plugin.run
      expect(plugin[:rackspace][:instance_id]).to eq("xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx")
    end

    it "logs error if instance id cannot be found" do
      expect(plugin)
        .to receive(:shell_out)
        .with("xenstore-read name")
        .and_raise(Ohai::Exceptions::Exec)

      expect(plugin.logger)
        .to receive(:trace)
        .with("Plugin Rackspace: Unable to find xenstore-read, cannot capture " \
             "instance ID information for Rackspace cloud")

      plugin.run

      expect(plugin[:rackspace]).not_to have_key(:instance_id)
    end
  end

  describe "with rackspace hint file" do
    before do
      allow(Resolv).to receive(:getname).and_raise(Resolv::ResolvError)
      allow(File).to receive(:exist?).with("/etc/resolv.conf").and_return(true)
      allow(File).to receive(:read).with("/etc/resolv.conf").and_return("")
      allow(plugin).to receive(:hint?).with("rackspace").and_return(true)
    end

    it_behaves_like "rackspace"

    describe "with no public interfaces (empty eth0)" do
      before do
        # unset public (eth0) addresses
        plugin[:network][:interfaces][:eth0]["addresses"] = {}
      end

      it "has correct rackspace attributes" do
        plugin.run
        # expliticly nil
        expect(plugin[:rackspace][:public_ip]).to be_nil
        expect(plugin[:rackspace][:public_ipv4]).to be_nil
        expect(plugin[:rackspace][:public_ipv6]).to be_nil
        expect(plugin[:rackspace][:public_hostname]).to be_nil
        # per normal
        expect(plugin[:rackspace][:local_ipv6]).to be_nil
        expect(plugin[:rackspace][:private_ip]).to eq("5.6.7.8")
        expect(plugin[:rackspace][:local_ipv4]).to eq("5.6.7.8")
        expect(plugin[:rackspace][:local_hostname]).to eq("katie")
      end
    end
  end

  describe "without hint file" do
    before do
      allow(plugin).to receive(:hint?).with("rackspace").and_return(false)
    end

    it_behaves_like "!rackspace"

  end

  describe "with Rackspace windows manufacturer data" do
    before do
      allow(plugin).to receive(:hint?).with("rackspace").and_return(false)
      allow(plugin).to receive(:has_rackspace_manufacturer?).and_return(true)
    end

    it "has rackspace attribute" do
      plugin.run
      expect(plugin[:rackspace]).not_to be_nil
    end

  end

  describe "xenstore provider returns rackspace" do
    before do
      stdout = "Rackspace\n"
      allow(plugin).to receive(:hint?).with("rackspace").and_return(false)
      allow(plugin).to receive(:shell_out).with("xenstore-read vm-data/provider_data/provider").and_return(mock_shell_out(0, stdout, "" ))
    end

    it_behaves_like "rackspace"

  end

  describe "xenstore provider does not return rackspace" do
    before do
      allow(plugin).to receive(:hint?).with("rackspace").and_return(false)
      stdout = "cumulonimbus\n"
      allow(plugin).to receive(:shell_out).with("xenstore-read vm-data/provider_data/provider").and_return(mock_shell_out(0, stdout, "" ))
    end

    it_behaves_like "!rackspace"

  end

  describe "xenstore provider does not exist" do
    before do
      allow(plugin).to receive(:hint?).with("rackspace").and_return(false)
      allow(plugin)
        .to receive(:shell_out)
        .with("xenstore-read vm-data/provider_data/provider")
        .and_raise(Ohai::Exceptions::Exec)
    end

    it_behaves_like "!rackspace"

  end

  describe "when private networks shell out fails" do
    it "logs an error and does not collect private_networks" do
      allow(plugin).to receive(:hint?).with("rackspace").and_return(true)

      expect(plugin)
        .to receive(:shell_out)
        .with("xenstore-ls vm-data/networking")
        .and_raise(Ohai::Exceptions::Exec)

      expect(plugin.logger)
        .to receive(:trace)
        .with("Plugin Rackspace: Unable to capture custom private networking " \
             "information for Rackspace cloud")

      plugin.run

      expect(plugin[:rackspace]).not_to have_key(:private_networks)
    end
  end

  describe "does not have private networks" do
    before do
      stdout = 'BC764E20422B = "{"label": "public"}"\n'
      allow(plugin).to receive(:hint?).with("rackspace").and_return(true)
      allow(plugin).to receive(:shell_out).with("xenstore-ls vm-data/networking").and_return(mock_shell_out(0, stdout, "" ))
      stdout = '{"label": "public", "broadcast": "9.10.11.255", "ips": [{"ip": "9.10.11.12", "netmask": "255.255.255.0", "enabled": "1", "gateway": null}], "mac": "BC:76:4E:20:42:2B", "dns": ["69.20.0.164", "69.20.0.196"], "gateway": null}'
      allow(plugin).to receive(:shell_out).with("xenstore-read vm-data/networking/BC764E20422B").and_return(mock_shell_out(0, stdout, "" ))
    end

    it "does not have private_networks object" do
      plugin.run
      expect(plugin[:rackspace][:private_networks]).to eq([])
    end
  end

  describe "has private networks" do
    before do
      plugin[:network][:interfaces][:eth2] = {
        addresses: {
          "fe80::be76:4eff:fe20:422b" => {
            "scope" => "Link",
            "prefixlen" => "64",
            "family" => "inet6",
         },
          "9.10.11.12" => {
            "broadcast" => "9.10.11.255",
            "netmask" => "255.255.255.0",
            "family" => "inet",
          },
          "BC:76:4E:20:42:2B" => {
            "family" => "lladdr",
          },
        },
      }
      stdout = 'BC764E20422B = "{"label": "private-network"}"\n'
      allow(plugin).to receive(:shell_out).with("xenstore-ls vm-data/networking").and_return(mock_shell_out(0, stdout, "" ))
      stdout = '{"label": "private-network", "broadcast": "9.10.11.255", "ips": [{"ip": "9.10.11.12", "netmask": "255.255.255.0", "enabled": "1", "gateway": null}], "mac": "BC:76:4E:20:42:2B", "dns": ["69.20.0.164", "69.20.0.196"], "gateway": null}'
      allow(plugin).to receive(:shell_out).with("xenstore-read vm-data/networking/BC764E20422B").and_return(mock_shell_out(0, stdout, "" ))
      allow(plugin).to receive(:hint?).with("rackspace").and_return(true)
    end

    it "has private_networks object" do
      plugin.run
      expect(plugin[:rackspace][:private_networks]).not_to be_nil
    end

    it "has correct values for all rackspace attributes" do
      plugin.run
      expect(plugin[:rackspace][:private_networks][0][:label]).to eq("private-network")
      expect(plugin[:rackspace][:private_networks][0][:broadcast]).to eq("9.10.11.255")
      expect(plugin[:rackspace][:private_networks][0][:mac]).to eq("BC:76:4E:20:42:2B")
    end

  end

end
