Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ignore errors to communicate with live cluster if it is not an explicit requirement #354

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions docs/CommonQueryPatterns.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ Patterns describing how to combine specific switches (global: `--<query_name> ,
`--<query_name> --resource_list <networkPolicies, namespaces and pods paths> --base_resource_list <networkpolicies, namespaces and pods path>` [see example here](../tests/k8s_cmdline_tests.yaml#L293-L302)

##### Handling missing resources:
- When running without any switch (i.e. `--<query_name>`), all resources will be loaded from k8s live cluster.
- When running without any switch (i.e. `--<query_name>`), nca checks if a communication with k8s live cluster is available, if yes - resources will be loaded from k8s live cluster,
otherwise, the query runs on empty resources (empty query result)

Running with switches:
- When running with specific topology switches only (using only `pod_list` and `ns_list`) without providing networkPolicy path, policies will be loaded from k8s live cluster
Expand Down Expand Up @@ -47,7 +48,8 @@ Running with switches:
`resourceList: [list of networkPolicies, namespaces and pods paths]` [see example here ](../tests/k8s_testcases/example_policies/resourcelist-one-path-example/resource-path-scheme.yaml#L3-L7)

##### Handling missing resources:
- If global scope does not exist, topology objects will be loaded from k8s live cluster.
- If global scope does not exist, nca checks if a communication with k8s live cluster is available, if yes - topology resources will be loaded from k8s live cluster,
otherwise, an empty peer container is created
- If `networkPolicyList` is not used and `resourceList` does not refer to any policy, a query reading this considers empty network-policies list.
- If global pods are missing (i.e. `podList` is not used and `resourceList` does not refer to any pod), global cluster will have 0 endpoints.
- If config's pods are missing, global pods will be used
Expand Down
50 changes: 42 additions & 8 deletions nca/NetworkConfig/ResourcesHandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import copy
from enum import Enum
from nca.FileScanners.GenericTreeScanner import TreeScannerFactory
from nca.Utils.CmdlineRunner import CmdlineRunner
from .NetworkConfig import NetworkConfig
from .PoliciesFinder import PoliciesFinder
from .TopologyObjectsFinder import PodsFinder, NamespacesFinder, ServicesFinder
Expand Down Expand Up @@ -83,14 +84,14 @@ def _set_config_peer_container(self, ns_list, pod_list, resource_list, config_na
peer_container = copy.deepcopy(self.global_peer_container)
else: # the specific networkConfig has no topology input resources (not private, neither global)
# this case is reachable when:
# 1. no input paths are provided at all, i.e. user intended to get resources from live cluster
# 1. no input paths are provided at all, then try to load from live cluster silently
# if communication fails then build an empty peer container
# 2. paths are provided only using resourceList flag, but no topology objects found;
# in this case we will not load topology from live cluster - keeping peer container empty
if resource_list is None: # getting here means ns_list and pod_list are None too
print('loading topology objects from k8s live cluster')
resources_parser.load_resources_from_k8s_live_cluster([ResourceType.Namespaces, ResourceType.Pods])
resources_parser.try_to_load_topology_from_live_cluster([ResourceType.Namespaces, ResourceType.Pods],
config_name)
peer_container = resources_parser.build_peer_container(config_name)

if save_flag: # if called from scheme with global topology or cmdline with 2 configs query
self.global_peer_container = peer_container
self.global_ns_finder = resources_parser.ns_finder
Expand Down Expand Up @@ -214,9 +215,8 @@ def parse_lists_for_policies(self, np_list, resource_list, peer_container):
elif resource_list:
self._parse_resources_path(resource_list, [ResourceType.Policies])
config_name = resource_list[0]
else: # running without any input flags means running on k8s live cluster
print('loading policies from k8s live cluster')
self.load_resources_from_k8s_live_cluster([ResourceType.Policies])
else: # running without any input flags - try to load from k8s live cluster silently
self.try_to_load_topology_from_live_cluster([ResourceType.Policies])

return config_name

Expand Down Expand Up @@ -257,7 +257,17 @@ def _parse_resources_path(self, resource_list, resource_flags):

self.policies_finder.parse_policies_in_parse_queue()

def load_resources_from_k8s_live_cluster(self, resource_flags):
def load_resources_from_k8s_live_cluster(self, resource_flags, run_silently=False):
"""
attempt to load the resources in resource_flags from k8s live cluster
:param list resource_flags: resource types to load from k8s live cluster
:param bool run_silently: indicates if this attempt should run silently, i.e. ignore errors if fails to
communicate.
"""
# Setting a flag in the CmdlineRunner to indicate if we are trying to load resources silently
# from the live cluster (e.g. global resources are missing)
CmdlineRunner.ignore_live_cluster_err = run_silently

if ResourceType.Namespaces in resource_flags:
self.ns_finder.load_ns_from_live_cluster()
if ResourceType.Pods in resource_flags:
Expand All @@ -268,6 +278,30 @@ def load_resources_from_k8s_live_cluster(self, resource_flags):
if ResourceType.Policies in resource_flags:
self.policies_finder.load_policies_from_k8s_cluster()

def try_to_load_topology_from_live_cluster(self, resources_flags, config_name='global'):
"""
an attempt to load resources from k8s live cluster silently.
in this case, communication with a k8s live cluster is not a must.
so the attempt occurs silently, if succeed to connect and load resources then a relevant message will be printed
otherwise, a warning message of not found resources will be printed
:param list resources_flags: resource types to load from k8s live cluster
:param str config_name: configuration name
"""
try:
self.load_resources_from_k8s_live_cluster(resources_flags, run_silently=True)
if ResourceType.Policies in resources_flags:
success = self.policies_finder.policies_container.policies
else:
success = self.ns_finder.namespaces or self.pods_finder.peer_set
except FileNotFoundError: # in case that kube-config file is not found
success = False # ignore the exception since this is a silent try

resource_names = ' and '.join(str(resource).split('.')[1].lower() for resource in resources_flags)
if success: # we got resources from live cluster
print(f'{config_name}: loading {resource_names} from k8s live cluster')
else:
print(f'Warning: {config_name} - {resource_names} were not found')

def _handle_calico_inputs(self, resource_flags):
if ResourceType.Namespaces in resource_flags:
self.ns_finder.load_ns_from_live_cluster()
Expand Down
25 changes: 19 additions & 6 deletions nca/Utils/CmdlineRunner.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,36 @@

import subprocess
import os
import sys
from fnmatch import fnmatch


class CmdlineRunner:
"""
A stateless class with only static functions to easily get k8s and calico resources using kubectl and calicoctl
"""
# a static variable to indicate if we want to ignore errors from running executable command - i.e. run silently
ignore_live_cluster_err = False

@staticmethod
def run_and_get_output(cmdline_list):
def run_and_get_output(cmdline_list, check_for_silent_exec=False):
"""
Run an executable with specific arguments and return its output to stdout
if a communicate error occurs, it will be ignored in case this is a silent try to communicate with live cluster,
otherwise, will be printed to stderr
:param list[str] cmdline_list: A list of arguments, the first of which is the executable path
:return: The executable's output to stdout
:param check_for_silent_exec: when true consider the static variable that indicates whether to ignore errors
or not
:return: The executable's output to stdout ( a list-resources on success, otherwise empty value)
:rtype: str
"""
cmdline_process = subprocess.Popen(cmdline_list, stdout=subprocess.PIPE)
return cmdline_process.communicate()[0]
cmdline_process = subprocess.Popen(cmdline_list, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
out, err = cmdline_process.communicate()
print_err_flag = \
not check_for_silent_exec or (check_for_silent_exec and not CmdlineRunner.ignore_live_cluster_err)
if err and print_err_flag:
print(err.decode().strip('\n'), file=sys.stderr)
return out

@staticmethod
def search_file_in_path(filename, search_path):
Expand Down Expand Up @@ -72,7 +85,7 @@ def locate_kube_config_file():
kube_config_file = os.path.join(home_dir, file)
os.environ['KUBECONFIG'] = kube_config_file
return
raise Exception('Failed to locate Kubernetes configuration files')
raise FileNotFoundError('Failed to locate Kubernetes configuration files')

@staticmethod
def get_k8s_resources(resource):
Expand All @@ -85,7 +98,7 @@ def get_k8s_resources(resource):
cmdline_list = ['kubectl', 'get', resource, '-o=json']
if resource in ['networkPolicy', 'authorizationPolicy', 'pod', 'ingress', 'Gateway', 'VirtualService', 'sidecar']:
cmdline_list.append('--all-namespaces')
return CmdlineRunner.run_and_get_output(cmdline_list)
return CmdlineRunner.run_and_get_output(cmdline_list, check_for_silent_exec=True)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't also be set True from get_calico_resources, not only from get_k8s_resources ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently if the topology section is missing / or user runs cmdline without resources or without policies - our default is to load missing resources from k8s live cluster - so it is the only relevant case for a silent attempt

we load from calico only if the input is calico - a provided "live cluster" input does not ignore errors for any of the layers


@staticmethod
def resolve_helm_chart(chart_dir):
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
namespaceList: ../../example_podlist/ns_list.json
podList: ../../example_podlist/pods_list.json

networkConfigList:
- name: sanity_np2
namespaceList: ../../example_podlist/ns_list.json
podList: ../../example_podlist/pods_list.json
networkPolicyList:
- sanity2-networkpolicy.yaml
expectedWarnings: 0
Expand Down