Skip to content

Commit

Permalink
Update check_node_status to support other NodeCondition (#309)
Browse files Browse the repository at this point in the history
  • Loading branch information
Mir Shahriar authored and tamalsaha committed Mar 16, 2018
1 parent 1b76502 commit c701f38
Show file tree
Hide file tree
Showing 3 changed files with 334 additions and 35 deletions.
124 changes: 89 additions & 35 deletions plugins/check_node_status/lib.go
Original file line number Diff line number Diff line change
@@ -1,78 +1,132 @@
package check_node_status

import (
"fmt"
"os"
"encoding/json"
"errors"

"github.com/appscode/go/flags"
"github.com/appscode/searchlight/pkg/icinga"
"github.com/appscode/searchlight/plugins"
"github.com/spf13/cobra"
core "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
"k8s.io/client-go/tools/clientcmd"
)

type Request struct {
masterURL string
kubeconfigPath string
type plugin struct {
client corev1.NodeInterface
options options
}

var _ plugins.PluginInterface = &plugin{}

Name string
func newPlugin(client corev1.NodeInterface, opts options) *plugin {
return &plugin{client, opts}
}

func CheckNodeStatus(req *Request) (icinga.State, interface{}) {
config, err := clientcmd.BuildConfigFromFlags(req.masterURL, req.kubeconfigPath)
func newPluginFromConfig(opts options) (*plugin, error) {
config, err := clientcmd.BuildConfigFromFlags(opts.masterURL, opts.kubeconfigPath)
if err != nil {
return icinga.Unknown, err
return nil, err
}
kubeClient := kubernetes.NewForConfigOrDie(config)
client := kubernetes.NewForConfigOrDie(config).CoreV1().Nodes()
return newPlugin(client, opts), nil
}

type options struct {
masterURL string
kubeconfigPath string
// Icinga host name
hostname string
// options for Secret
nodeName string
}

node, err := kubeClient.CoreV1().Nodes().Get(req.Name, metav1.GetOptions{})
func (o *options) validate() error {
host, err := icinga.ParseHost(o.hostname)
if err != nil {
return icinga.Unknown, err
return errors.New("invalid icinga host.name")
}
if host.Type != icinga.TypeNode {
return errors.New("invalid icinga host type")
}
o.nodeName = host.ObjectName
return nil
}

if node == nil {
return icinga.Critical, "Node not found"
type message struct {
Ready core.ConditionStatus `json:"ready,omitempty"`
OutOfDisk core.ConditionStatus `json:"outOfDisk,omitempty"`
MemoryPressure core.ConditionStatus `json:"memoryPressure,omitempty"`
DiskPressure core.ConditionStatus `json:"diskPressure,omitempty"`
NetworkUnavailable core.ConditionStatus `json:"networkUnavailable,omitempty"`
}

func (p *plugin) Check() (icinga.State, interface{}) {
node, err := p.client.Get(p.options.nodeName, metav1.GetOptions{})
if err != nil {
return icinga.Unknown, err
}

msg := message{}
for _, condition := range node.Status.Conditions {
if condition.Type == core.NodeReady && condition.Status == core.ConditionFalse {
return icinga.Critical, "Node is not Ready"
switch condition.Type {
case core.NodeReady:
msg.Ready = condition.Status
case core.NodeOutOfDisk:
msg.OutOfDisk = condition.Status
case core.NodeMemoryPressure:
msg.MemoryPressure = condition.Status
case core.NodeDiskPressure:
msg.DiskPressure = condition.Status
case core.NodeNetworkUnavailable:
msg.NetworkUnavailable = condition.Status
}
}

return icinga.OK, "Node is Ready"
var state icinga.State
if msg.Ready == core.ConditionFalse {
state = icinga.Critical
} else if msg.OutOfDisk == core.ConditionTrue ||
msg.MemoryPressure == core.ConditionTrue ||
msg.DiskPressure == core.ConditionTrue ||
msg.NetworkUnavailable == core.ConditionTrue {
state = icinga.Critical
}

output, err := json.MarshalIndent(msg, "", " ")
if err != nil {
return icinga.Unknown, err
}

return state, string(output)
}

func NewCmd() *cobra.Command {
var req Request
var icingaHost string
var opts options

c := &cobra.Command{
Use: "check_node_status",
Short: "Check Kubernetes Node",
Example: "",
Use: "check_node_status",
Short: "Check Kubernetes Node",

Run: func(cmd *cobra.Command, args []string) {
flags.EnsureRequiredFlags(cmd, "host")

host, err := icinga.ParseHost(icingaHost)
if err != nil {
fmt.Fprintln(os.Stdout, icinga.Warning, "Invalid icinga host.name")
os.Exit(3)
if err := opts.validate(); err != nil {
icinga.Output(icinga.Unknown, err)
}
if host.Type != icinga.TypeNode {
fmt.Fprintln(os.Stdout, icinga.Warning, "Invalid icinga host type")
os.Exit(3)
plugin, err := newPluginFromConfig(opts)
if err != nil {
icinga.Output(icinga.Unknown, err)
}
req.Name = host.ObjectName
icinga.Output(CheckNodeStatus(&req))
icinga.Output(plugin.Check())
},
}

c.Flags().StringVar(&req.masterURL, "master", req.masterURL, "The address of the Kubernetes API server (overrides any value in kubeconfig)")
c.Flags().StringVar(&req.kubeconfigPath, "kubeconfig", req.kubeconfigPath, "Path to kubeconfig file with authorization information (the master location is set by the master flag).")

c.Flags().StringVarP(&icingaHost, "host", "H", "", "Icinga host name")
c.Flags().StringVar(&opts.masterURL, "master", opts.masterURL, "The address of the Kubernetes API server (overrides any value in kubeconfig)")
c.Flags().StringVar(&opts.kubeconfigPath, "kubeconfig", opts.kubeconfigPath, "Path to kubeconfig file with authorization information (the master location is set by the master flag).")
c.Flags().StringVarP(&opts.hostname, "host", "H", "", "Icinga host name")
return c
}
212 changes: 212 additions & 0 deletions plugins/check_node_status/lib_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
package check_node_status

import (
"github.com/appscode/searchlight/pkg/icinga"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
core "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
corev1 "k8s.io/client-go/kubernetes/typed/core/v1"
)

var _ = Describe("check_cert", func() {
var node *core.Node
var client corev1.NodeInterface
var opts options

BeforeEach(func() {
client = cs.CoreV1().Nodes()
node = &core.Node{
ObjectMeta: metav1.ObjectMeta{
Name: "node",
},
}
opts = options{
nodeName: node.Name,
}
})

AfterEach(func() {
if client != nil {
client.Delete(node.Name, &metav1.DeleteOptions{})
}
})

Describe("there is a ready node", func() {
Context("with no other problems", func() {
It("should be OK", func() {
_, err := client.Create(node)
Expect(err).ShouldNot(HaveOccurred())

node.Status.Conditions = []core.NodeCondition{
{
Type: core.NodeReady,
Status: core.ConditionTrue,
},
}
_, err = client.Update(node)
Expect(err).ShouldNot(HaveOccurred())

state, _ := newPlugin(client, opts).Check()
Expect(state).Should(BeIdenticalTo(icinga.OK))
})
})
Context("with other problems", func() {
It("such as OutOfDisk", func() {
_, err := client.Create(node)
Expect(err).ShouldNot(HaveOccurred())

node.Status.Conditions = []core.NodeCondition{
{
Type: core.NodeReady,
Status: core.ConditionTrue,
},
{
Type: core.NodeOutOfDisk,
Status: core.ConditionTrue,
},
}
_, err = client.Update(node)
Expect(err).ShouldNot(HaveOccurred())

state, _ := newPlugin(client, opts).Check()
Expect(state).Should(BeIdenticalTo(icinga.Critical))
})
It("such as OutOfDisk, MemoryPressure", func() {
_, err := client.Create(node)
Expect(err).ShouldNot(HaveOccurred())

node.Status.Conditions = []core.NodeCondition{
{
Type: core.NodeReady,
Status: core.ConditionTrue,
},
{
Type: core.NodeOutOfDisk,
Status: core.ConditionTrue,
},
{
Type: core.NodeMemoryPressure,
Status: core.ConditionTrue,
},
{
Type: core.NodeDiskPressure,
Status: core.ConditionTrue,
},
{
Type: core.NodeNetworkUnavailable,
Status: core.ConditionTrue,
},
}
_, err = client.Update(node)
Expect(err).ShouldNot(HaveOccurred())

state, _ := newPlugin(client, opts).Check()
Expect(state).Should(BeIdenticalTo(icinga.Critical))
})
})
})

Describe("there is a not ready node", func() {
JustBeforeEach(func() {
client = cs.CoreV1().Nodes()
opts = options{
nodeName: node.Name,
}
})
Context("with no other problems", func() {
It("should be Critical", func() {
_, err := client.Create(node)
Expect(err).ShouldNot(HaveOccurred())

node.Status.Conditions = []core.NodeCondition{
{
Type: core.NodeReady,
Status: core.ConditionFalse,
},
}
_, err = client.Update(node)
Expect(err).ShouldNot(HaveOccurred())

state, _ := newPlugin(client, opts).Check()
Expect(state).Should(BeIdenticalTo(icinga.Critical))
})
})
Context("with other problems", func() {
It("such as OutOfDisk", func() {
_, err := client.Create(node)
Expect(err).ShouldNot(HaveOccurred())

node.Status.Conditions = []core.NodeCondition{
{
Type: core.NodeReady,
Status: core.ConditionFalse,
},
{
Type: core.NodeOutOfDisk,
Status: core.ConditionTrue,
},
}
_, err = client.Update(node)
Expect(err).ShouldNot(HaveOccurred())

state, _ := newPlugin(client, opts).Check()
Expect(state).Should(BeIdenticalTo(icinga.Critical))
})
It("such as OutOfDisk, MemoryPressure", func() {
_, err := client.Create(node)
Expect(err).ShouldNot(HaveOccurred())

node.Status.Conditions = []core.NodeCondition{
{
Type: core.NodeReady,
Status: core.ConditionFalse,
},
{
Type: core.NodeOutOfDisk,
Status: core.ConditionTrue,
},
{
Type: core.NodeMemoryPressure,
Status: core.ConditionTrue,
},
}
_, err = client.Update(node)
Expect(err).ShouldNot(HaveOccurred())

state, _ := newPlugin(client, opts).Check()
Expect(state).Should(BeIdenticalTo(icinga.Critical))
})
})
})

Describe("Check validation", func() {
Context("for invalid", func() {
It("with invalid part", func() {
opts = options{
hostname: "demo@node",
}
err := opts.validate()
Expect(err).Should(HaveOccurred())
})
It("with invalid type", func() {
opts = options{
hostname: "demo@cluster",
}
err := opts.validate()
Expect(err).Should(HaveOccurred())
})
})
Context("for valid", func() {
It("with valid name", func() {
opts = options{
hostname: "demo@node@node",
}
err := opts.validate()
Expect(err).ShouldNot(HaveOccurred())

Expect(opts.nodeName).Should(Equal("node"))
})
})
})
})
Loading

0 comments on commit c701f38

Please sign in to comment.