summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRenato Covarrubias <rnt@rnt.cl>2023-02-14 22:10:34 +0100
committerGitHub <noreply@github.com>2023-02-14 13:10:34 -0800
commitdb9c5e48a741f6106a6575d5cb41db3fe0d1fb77 (patch)
treed0d38b78040bed44b5c9542dcb00dea3d827fb4a
parentaf1405ed41ca25bcc2967bcf97a0448d5cba208d (diff)
downloadohai-db9c5e48a741f6106a6575d5cb41db3fe0d1fb77.tar.gz
Add cloud provider oci (#1780)
ohai cloud fail if the vm is in Oracle Cloud Infrastructure (OCI). This PR add support to this cloud provider to cloud plugin. It also adds OCI detection to the cloud plugin. Signed-off-by: Renato Covarrubias <rnt@rnt.cl>
-rw-r--r--.github/workflows/exec.yml2
-rw-r--r--lib/ohai/mixin/oci_metadata.rb69
-rw-r--r--lib/ohai/plugins/cloud.rb22
-rw-r--r--lib/ohai/plugins/oci.rb94
-rw-r--r--spec/unit/mixin/oci_metadata_spec.rb66
-rw-r--r--spec/unit/plugins/cloud_spec.rb38
-rw-r--r--spec/unit/plugins/oci_spec.rb198
7 files changed, 488 insertions, 1 deletions
diff --git a/.github/workflows/exec.yml b/.github/workflows/exec.yml
index 13dca0ca..ba734d5f 100644
--- a/.github/workflows/exec.yml
+++ b/.github/workflows/exec.yml
@@ -6,7 +6,7 @@ name: exec
push:
branches:
- main
- - 1=7-stable
+ - 17-stable
- 16-stable
permissions:
diff --git a/lib/ohai/mixin/oci_metadata.rb b/lib/ohai/mixin/oci_metadata.rb
new file mode 100644
index 00000000..b04243a3
--- /dev/null
+++ b/lib/ohai/mixin/oci_metadata.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+#
+# Author:: Renato Covarrubias (<rnt@rnt.cl>)
+# 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 "net/http" unless defined?(Net::HTTP)
+
+module Ohai
+ module Mixin
+ module OCIMetadata
+ OCI_METADATA_ADDR = "169.254.169.254"
+ OCI_METADATA_URL = "/opc/v2"
+ CHASSIS_ASSET_TAG_FILE = "/sys/devices/virtual/dmi/id/chassis_asset_tag"
+
+ # fetch the meta content with a timeout and the required header
+ def http_get(uri)
+ conn = Net::HTTP.start(OCI_METADATA_ADDR)
+ conn.read_timeout = 6
+ conn.get(
+ uri,
+ {
+ "Authorization" => "Bearer Oracle",
+ "User-Agent" => "chef-ohai/#{Ohai::VERSION}",
+ }
+ )
+ end
+
+ # parse JSON data from a String to a Hash
+ #
+ # @param [String] response_body json as string to parse
+ #
+ # @return [Hash]
+ def parse_json(response_body)
+ data = String(response_body)
+ parser = FFI_Yajl::Parser.new
+ parser.parse(data)
+ rescue FFI_Yajl::ParseError
+ logger.warn("Mixin OciMetadata: Metadata response is NOT valid JSON")
+ nil
+ end
+
+ # Fetch metadata from api
+ def fetch_metadata(metadata = "instance")
+ response = http_get("#{OCI_METADATA_URL}/#{metadata}")
+ return nil unless response.code == "200"
+
+ if response.code == "200"
+ parse_json(response.body)
+ else
+ logger.warn("Mixin OciMetadata: Received response code #{response.code} requesting metadata")
+ nil
+ end
+ end
+ end
+ end
+end
diff --git a/lib/ohai/plugins/cloud.rb b/lib/ohai/plugins/cloud.rb
index 8e79a824..503516ab 100644
--- a/lib/ohai/plugins/cloud.rb
+++ b/lib/ohai/plugins/cloud.rb
@@ -28,6 +28,7 @@ Ohai.plugin(:Cloud) do
depends "azure"
depends "digital_ocean"
depends "softlayer"
+ depends "oci"
# Class to help enforce the interface exposed to node[:cloud] (OHAI-542)
#
@@ -336,6 +337,26 @@ Ohai.plugin(:Cloud) do
@cloud_attr_obj.provider = "softlayer"
end
+ # ----------------------------------------
+ # OCI
+ # ----------------------------------------
+
+ # Is current Oracle Cloud Infrastructure?
+ #
+ # === Return
+ # true:: If oci Hash is defined
+ # false:: Otherwise
+ def on_oci?
+ oci != nil
+ end
+
+ # Fill cloud hash with OCI values
+ def oci_values
+ oci["metadata"]["network"]["interface"].each { |vnic| @cloud_attr_obj.add_ipv4_addr(vnic["privateIp"], :private) }
+ @cloud_attr_obj.local_hostname = oci["metadata"]["compute"]["hostname"]
+ @cloud_attr_obj.provider = "oci"
+ end
+
collect_data do
require "ipaddr" unless defined?(IPAddr)
@@ -351,6 +372,7 @@ Ohai.plugin(:Cloud) do
get_digital_ocean_values if on_digital_ocean?
get_softlayer_values if on_softlayer?
get_alibaba_values if on_alibaba?
+ oci_values if on_oci?
cloud @cloud_attr_obj.cloud_mash
end
diff --git a/lib/ohai/plugins/oci.rb b/lib/ohai/plugins/oci.rb
new file mode 100644
index 00000000..04e83ba5
--- /dev/null
+++ b/lib/ohai/plugins/oci.rb
@@ -0,0 +1,94 @@
+# frozen_string_literal: true
+
+#
+# Author:: Renato Covarrubias (<rnt@rnt.cl>)
+# 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.
+#
+
+Ohai.plugin(:Oci) do
+ require_relative "../mixin/oci_metadata"
+ require_relative "../mixin/http_helper"
+
+ include Ohai::Mixin::OCIMetadata
+ include Ohai::Mixin::HttpHelper
+
+ provides "oci"
+
+ collect_data do
+ oci_metadata_from_hints = hint?("oci")
+ if oci_metadata_from_hints
+ logger.trace("Plugin OCI: oci hint is present. Parsing any hint data.")
+ oci Mash.new
+ oci_metadata_from_hints.each { |k, v| oci[k] = v }
+ oci["metadata"] = parse_metadata
+ elsif oci_chassis_asset_tag?
+ logger.trace("Plugin oci: No hints present, but system appears to be on oci.")
+ oci Mash.new
+ oci["metadata"] = parse_metadata
+ else
+ logger.trace("Plugin oci: No hints present and doesn't appear to be on oci.")
+ false
+ end
+ end
+
+ def oci_chassis_asset_tag?
+ has_oci_chassis_asset_tag = false
+ if file_exist?(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE)
+ file_open(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE).each do |line|
+ next unless /OracleCloud.com/.match?(line)
+
+ logger.trace("Plugin oci: Found OracleCloud.com chassis_asset_tag used by oci.")
+ has_oci_chassis_asset_tag = true
+ break
+ end
+ end
+ has_oci_chassis_asset_tag
+ end
+
+ def parse_metadata
+ return nil unless can_socket_connect?(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR, 80)
+
+ instance_data = fetch_metadata("instance")
+ return nil if instance_data.nil?
+
+ metadata = Mash.new
+ metadata["compute"] = Mash.new
+
+ instance_data.each do |k, v|
+ metadata["compute"][k] = v
+ end
+
+ vnics_data = fetch_metadata("vnics")
+
+ unless vnics_data.nil?
+ metadata["network"] = Mash.new
+ metadata["network"]["interface"] = []
+ vnics_data.each do |v|
+ metadata["network"]["interface"].append(v)
+ end
+ end
+
+ volume_attachments_data = fetch_metadata("volumeAttachments")
+
+ unless volume_attachments_data.nil?
+ metadata["volumes"] = Mash.new
+ volume_attachments_data.each do |k, v|
+ metadata["volumes"][k] = v
+ end
+ end
+
+ metadata
+ end
+end
diff --git a/spec/unit/mixin/oci_metadata_spec.rb b/spec/unit/mixin/oci_metadata_spec.rb
new file mode 100644
index 00000000..3135b028
--- /dev/null
+++ b/spec/unit/mixin/oci_metadata_spec.rb
@@ -0,0 +1,66 @@
+# frozen_string_literal: true
+
+#
+# Author:: Renato Covarrubias <rnt@rnt.cl>
+# 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 CONDIT"Net::HTTP Response"NS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "spec_helper"
+require "ohai/mixin/oci_metadata"
+
+describe Ohai::Mixin::OCIMetadata do
+ let(:mixin) do
+ mixin = Object.new.extend(Ohai::Mixin::OCIMetadata)
+ mixin
+ end
+
+ before do
+ logger = instance_double("Mixlib::Log::Child", trace: nil, debug: nil, warn: nil)
+ allow(mixin).to receive(:logger).and_return(logger)
+ end
+
+ describe "#http_get" do
+ it "gets the passed URI" do
+ http_mock = double("http")
+ allow(http_mock).to receive(:read_timeout=)
+ allow(Net::HTTP).to receive(:start).with(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR).and_return(http_mock)
+
+ expect(http_mock).to receive(:get).with(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR,
+ { "Authorization" => "Bearer Oracle",
+ "User-Agent" => "chef-ohai/#{Ohai::VERSION}" })
+ mixin.http_get(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR)
+ end
+ end
+
+ describe "#fetch_metadata" do
+ it "returns an empty hash given a non-200 response" do
+ http_mock = double("http", { code: "404" })
+ allow(mixin).to receive(:http_get).and_return(http_mock)
+
+ expect(mixin.logger).not_to receive(:warn)
+ vals = mixin.fetch_metadata
+ expect(vals).to eq(nil)
+ end
+
+ it "returns a populated hash given valid JSON response" do
+ http_mock = double("http", { code: "200", body: '{ "foo": "bar"}' })
+ allow(mixin).to receive(:http_get).and_return(http_mock)
+
+ expect(mixin.logger).not_to receive(:warn)
+ vals = mixin.fetch_metadata
+ expect(vals).to eq({ "foo" => "bar" })
+ end
+ end
+end
diff --git a/spec/unit/plugins/cloud_spec.rb b/spec/unit/plugins/cloud_spec.rb
index 95d210e0..82dee74f 100644
--- a/spec/unit/plugins/cloud_spec.rb
+++ b/spec/unit/plugins/cloud_spec.rb
@@ -87,6 +87,7 @@ describe Ohai::System, "plugin cloud" do
@plugin[:gce] = nil
@plugin[:digital_ocean] = nil
@plugin[:softlayer] = nil
+ @plugin[:oci] = nil
@plugin.run
expect(@plugin[:cloud]).to be_nil
end
@@ -511,4 +512,41 @@ describe Ohai::System, "plugin cloud" do
end
end
+ describe "with OCI mash" do
+ before do
+ @plugin[:oci] = Mash.new
+ @plugin[:oci][:metadata] = {
+ "compute" => {
+ "hostname" => "my-hostname",
+ },
+ "network" => {
+ "interface" => [
+ { "vnicId" => "ocid1.vnic.oc1.phx.exampleuniqueID", "privateIp" => "10.0.3.6", "vlanTag" => 11,
+ "macAddr" => "00:00:00:00:00:01", "virtualRouterIp" => "10.0.3.1", "subnetCidrBlock" => "10.0.3.0/24",
+ "nicIndex" => 0 },
+ ],
+ },
+ }
+ end
+
+ it "doesn't populates cloud vm_name" do
+ @plugin.run
+ expect(@plugin[:cloud][:vm_name]).not_to eq("testtest")
+ end
+
+ it "populates cloud local_hostname" do
+ @plugin.run
+ expect(@plugin[:cloud][:local_hostname]).to eq("my-hostname")
+ end
+
+ it "populates cloud private ip" do
+ @plugin.run
+ expect(@plugin[:cloud][:local_ipv4]).to eq(@plugin[:oci][:metadata][:network][:interface][0]["privateIp"])
+ end
+
+ it "populates cloud provider" do
+ @plugin.run
+ expect(@plugin[:cloud][:provider]).to eq("oci")
+ end
+ end
end
diff --git a/spec/unit/plugins/oci_spec.rb b/spec/unit/plugins/oci_spec.rb
new file mode 100644
index 00000000..53737383
--- /dev/null
+++ b/spec/unit/plugins/oci_spec.rb
@@ -0,0 +1,198 @@
+# frozen_string_literal: true
+
+#
+# Author:: Kaustubh Deorukhkar (<kaustubh@clogeny.com>)
+# Copyright:: Copyright (c) Chef Software Inc.
+# 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 "spec_helper"
+begin
+ require "win32/registry" unless defined?(Win32::Registry)
+rescue LoadError => e
+ puts "Skipping missing rake dep: #{e}"
+end
+
+describe Ohai::System, "plugin oci" do
+ let(:plugin) { get_plugin("oci") }
+ let(:hint) do
+ {
+ "local_hostname" => "test-vm",
+ "provider" => "oci",
+ }
+ end
+
+ let(:response_data) do
+ {
+ "compute" => {
+ "availabilityDomain" => "EMIr:PHX-AD-1",
+ "faultDomain" => "FAULT-DOMAIN-3",
+ "compartmentId" => "ocid1.tenancy.oc1..exampleuniqueID",
+ "displayName" => "my-example-instance",
+ "hostname" => "my-hostname",
+ "id" => "ocid1.instance.oc1.phx.exampleuniqueID",
+ "image" => "ocid1.image.oc1.phx.exampleuniqueID",
+ "metadata" => {
+ "ssh_authorized_keys" => "example-ssh-key",
+ },
+ "region" => "phx",
+ "canonicalRegionName" => "us-phoenix-1",
+ "ociAdName" => "phx-ad-1",
+ "regionInfo" => {
+ "realmKey" => "oc1",
+ "realmDomainComponent" => "oraclecloud.com",
+ "regionKey" => "PHX",
+ "regionIdentifier" => "us-phoenix-1",
+ },
+ "shape" => "VM.Standard.E3.Flex",
+ "state" => "Running",
+ "timeCreated" => 1_600_381_928_581,
+ "agentConfig" => {
+ "monitoringDisabled" => false,
+ "managementDisabled" => false,
+ "allPluginsDisabled" => false,
+ "pluginsConfig" => [
+ { "name" => "OS Management Service Agent", "desiredState" => "ENABLED" },
+ { "name" => "Custom Logs Monitoring", "desiredState" => "ENABLED" },
+ { "name" => "Compute Instance Run Command", "desiredState" => "ENABLED" },
+ { "name" => "Compute Instance Monitoring", "desiredState" => "ENABLED" },
+ ],
+ },
+ "freeformTags" => {
+ "Department" => "Finance",
+ },
+ "definedTags" => {
+ "Operations" => {
+ "CostCenter" => "42",
+ },
+ },
+ },
+ "network" => {
+ "interface" => [
+ { "vnicId" => "ocid1.vnic.oc1.phx.exampleuniqueID", "privateIp" => "10.0.3.6", "vlanTag" => 11,
+ "macAddr" => "00:00:00:00:00:01", "virtualRouterIp" => "10.0.3.1", "subnetCidrBlock" => "10.0.3.0/24",
+ "nicIndex" => 0 },
+ { "vnicId" => "ocid1.vnic.oc1.phx.exampleuniqueID", "privateIp" => "10.0.4.3", "vlanTag" => 12,
+ "macAddr" => "00:00:00:00:00:02", "virtualRouterIp" => "10.0.4.1", "subnetCidrBlock" => "10.0.4.0/24",
+ "nicIndex" => 0 },
+ ],
+ },
+ }
+ end
+
+ before do
+ # skips all the metadata logic unless we want to test it
+ allow(plugin).to receive(:can_socket_connect?)
+ .with(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR, 80)
+ .and_return(false)
+ end
+
+ shared_examples_for "!oci" do
+ it "does not set the oci attribute" do
+ plugin.run
+ expect(plugin[:oci]).to be_nil
+ end
+ end
+
+ shared_examples_for "oci" do
+ it "sets the oci attribute" do
+ plugin.run
+ expect(plugin[:oci]).to be_truthy
+ expect(plugin[:oci]).to have_key(:metadata)
+ end
+ end
+
+ describe "with oci hint file" do
+ before do
+ allow(plugin).to receive(:hint?).with("oci").and_return(hint)
+ end
+
+ it "sets the oci cloud attributes" do
+ plugin.run
+ expect(plugin[:oci]["provider"]).to eq("oci")
+ expect(plugin[:oci]["local_hostname"]).to eq("test-vm")
+ end
+ end
+
+ describe "without oci hint file not in OCI" do
+ before do
+ allow(plugin).to receive(:hint?).with("oci").and_return(false)
+ allow(plugin).to receive(:file_exist?).with(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE).and_return(true)
+ @double_file = double(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE)
+ allow(@double_file).to receive(:each)
+ .and_yield("")
+ allow(plugin).to receive(:file_open).with(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE).and_return(@double_file)
+ end
+
+ it_behaves_like "!oci"
+ end
+
+ describe "without oci hint file in OCI" do
+ before do
+ allow(plugin).to receive(:hint?).with("oci").and_return(false)
+ allow(plugin).to receive(:file_exist?).with(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE).and_return(true)
+ @double_file = double(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE)
+ allow(@double_file).to receive(:each)
+ .and_yield("OracleCloud.com")
+ allow(plugin).to receive(:file_open).with(Ohai::Mixin::OCIMetadata::CHASSIS_ASSET_TAG_FILE).and_return(@double_file)
+ end
+
+ it_behaves_like "oci"
+ end
+
+ describe "with non-responsive metadata endpoint" do
+ before do
+ allow(plugin).to receive(:hint?).with("oci").and_return({})
+ end
+
+ it "does not return metadata information" do
+ allow(plugin).to receive(:can_socket_connect?)
+ .with(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR, 80)
+ .and_return(true)
+ allow(plugin).to receive(:parse_metadata).and_return(nil)
+
+ plugin.run
+ expect(plugin[:oci]).to have_key(:metadata)
+ expect(plugin[:oci][:metadata]).to be_nil
+ end
+ end
+
+ describe "with responsive metadata endpoint" do
+ before do
+ allow(plugin).to receive(:hint?).with("oci").and_return({})
+ allow(plugin).to receive(:can_socket_connect?)
+ .with(Ohai::Mixin::OCIMetadata::OCI_METADATA_ADDR, 80)
+ .and_return(true)
+ allow(plugin).to receive(:parse_metadata).and_return(response_data)
+ plugin.run
+ end
+
+ it "returns metadata compute information" do
+ expect(plugin[:oci][:metadata][:compute][:availabilityDomain]).to eq("EMIr:PHX-AD-1")
+ expect(plugin[:oci][:metadata][:compute][:compartmentId]).to eq("ocid1.tenancy.oc1..exampleuniqueID")
+ expect(plugin[:oci][:metadata][:compute][:faultDomain]).to eq("FAULT-DOMAIN-3")
+ expect(plugin[:oci][:metadata][:compute][:hostname]).to eq("my-hostname")
+ expect(plugin[:oci][:metadata][:compute][:image]).to eq("ocid1.image.oc1.phx.exampleuniqueID")
+ expect(plugin[:oci][:metadata][:compute][:region]).to eq("phx")
+ expect(plugin[:oci][:metadata][:compute][:shape]).to eq("VM.Standard.E3.Flex")
+ expect(plugin[:oci][:metadata][:compute][:state]).to eq("Running")
+ end
+
+ it "returns metadata network information" do
+ expect(plugin[:oci][:metadata][:network][:interface][0][:macAddr]).to eq("00:00:00:00:00:01")
+ expect(plugin[:oci][:metadata][:network][:interface][0][:privateIp]).to eq("10.0.3.6")
+ end
+ end
+end