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

adding an option for the filter ipsubnet and testing #40670

Merged
merged 27 commits into from
Aug 30, 2018
Merged

adding an option for the filter ipsubnet and testing #40670

merged 27 commits into from
Aug 30, 2018

Conversation

pierremahot
Copy link
Contributor

SUMMARY

Adding the option to request the index of a subnet in a bigger subnet

ISSUE TYPE
  • Feature Pull Request
COMPONENT NAME

filter ipsubnet

ANSIBLE VERSION
ansible 2.5.2
  config file = ansible.cfg
  python version = 2.7.9 default, jun 29 2016, 13:08:31) [GCC 4.9.2]
ADDITIONAL INFORMATION

Need this feature to deduce easily a vlan for my switch

Examples:
---
- hosts: local
  tasks:
    - debug: msg="{{ '192.168.50.0/24' | ipsubnet('192.168.0.0/16') }}"
#display 51
    - debug: msg="{{ '10.10.10.8/30' | ipsubnet('192.168.0.0/16')}}
#display False
   - debug: msg="{{ '10.10.10.8/16' | ipsubnet('10.10.0.99/24')}}
#display False
   - debug: msg="{{ '10.10.0.9/30' | ipsubnet('10.10.0.99/24')}}
#display 3

@ansibot ansibot added affects_2.6 This issue/PR affects Ansible v2.6 feature This issue/PR relates to a feature request. needs_triage Needs a first human triage before being processed. support:core This issue/PR relates to code supported by the Ansible Engineering Team. labels May 24, 2018
@ansibot ansibot added the test This PR relates to tests. label May 24, 2018
@samdoran samdoran added networking Network category and removed needs_triage Needs a first human triage before being processed. labels May 24, 2018
def ipsubnet(value, query='', index='x'):
strquery = str(query)
Copy link
Member

Choose a reason for hiding this comment

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

Please don't add anything before a docstring.

@@ -734,8 +738,7 @@ def ipsubnet(value, query='', index='x'):

if not query:
Copy link
Member

Choose a reason for hiding this comment

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

better add it above this line

@ansibot ansibot added the needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. label May 24, 2018
@mattclay
Copy link
Member

CI failure in unit tests: https://app.shippable.com/github/ansible/ansible/runs/67119/3/tests

>       self.assertEqual(ipsubnet(address, subnet), False)
E       AssertionError: '35' != False

test/units/plugins/filter/test_ipaddr.py:490: AssertionError

@mattclay mattclay added the ci_verified Changes made in this PR are causing tests to fail. label May 25, 2018
@pierremahot pierremahot reopened this May 25, 2018
@ansibot ansibot removed the ci_verified Changes made in this PR are causing tests to fail. label May 25, 2018
@pierremahot pierremahot reopened this May 26, 2018
@@ -735,10 +734,11 @@ def ipsubnet(value, query='', index='x'):
value = netaddr.IPNetwork(v)
except:
return False

querystr = str(query)
Copy link
Member

Choose a reason for hiding this comment

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

Also, since in Python we use snake_case for variables/functions, please change this to query_string

v = ipaddr(query, 'subnet')

query = netaddr.IPNetwork(v)
except:
Copy link
Member

Choose a reason for hiding this comment

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

It is a bad practice to catch all exceptions wholesale. You mask different errors, which you should've processed correctly. Also this makes it almost impossible to debug misbehavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I just make the same code as before line 727 to 736 but I am not sure how to cut this the good way :

        try:
            vtype = ipaddr(query, 'type')
       except:
            return False

        if vtype == 'address':
            try:
                v = ipaddr(query, 'cidr')
            except:
                return False
        elif vtype == 'network':
            try:
                v = ipaddr(query, 'subnet')
            except:
                return False
        query = netaddr.IPNetwork(v)

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I see that, but it's not a good approach.
From https://github.com/drkjam/netaddr/blob/ebf714601e8cd6234575422f876d4d579b202b1a/netaddr/ip/__init__.py#L938 it looks like netaddr.IPNetwork(v) may raise AddrFormatError.

But if you want to catch all netaddr library's thrown exceptions, you may do:

from netaddr.core import AddrConversionError, AddrFormatError, NotRegisteredError
NETADDR_ERRORS = AddrConversionError, AddrFormatError, NotRegisteredError
...
try:
    ...your exceptional code here
except NETADDR_ERRORS as err:
    process_or_log(err)
    # optionally raise/pass/return False (whatever fits)

Copy link
Member

Choose a reason for hiding this comment

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

I still insist on correct exception handling.

if network_in_network(query, value):
subnetlist = list(query.subnet(value.prefixlen))
for i in range(0, len(subnetlist)):
if (subnetlist[i] == value):
Copy link
Member

Choose a reason for hiding this comment

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

In Python, we don't wrap clauses with parentheses. Please remove them.


if network_in_network(query, value):
subnetlist = list(query.subnet(value.prefixlen))
for i in range(0, len(subnetlist)):
Copy link
Member

@webknjaz webknjaz May 26, 2018

Choose a reason for hiding this comment

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

What you're trying to do is possible to achieve using "sliding window" technique with enumerate(), it will be possible to even drop dancing around "index+1" problem:

for i, subnet in enumerate(query.subnet(value.prefixlen)):
    if subnet == value:
        return str(i+1)

return False

if network_in_network(query, value):
subnetlist = list(query.subnet(value.prefixlen))
Copy link
Member

Choose a reason for hiding this comment

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

According to the doc, it's a generator: https://netaddr.readthedocs.io/en/latest/api.html#netaddr.IPNetwork.subnet

So you can iterate over it, no need to list() it. Please remove it.

except:
return False

if network_in_network(query, value):
Copy link
Member

Choose a reason for hiding this comment

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

I believe, you don't even need the if-wrapper, if query.subnet won't yield anything, the loop will be "skipped", just like your intention is.

@ansibot
Copy link
Contributor

ansibot commented May 29, 2018

@pierremahot This PR contains @ mentions in at least one commit message. Those mentions can cause cascading notifications through GitHub and need to be removed. Please squash or amend your commits to remove the mentions.

click here for bot help

@pierremahot
Copy link
Contributor Author

pierremahot commented May 30, 2018

It's my choice i prefert to have the first subnet to have the value 1 and not 0.
And with the enumerate i can return i insted of i + 1

@@ -782,7 +782,7 @@ def ipsubnet(value, query='', index='x'):
except:
return False

for i, subnet in enumerate(query.subnet(value.prefixlen)):
for i, subnet in enumerate(query.subnet(value.prefixlen), 1):
Copy link
Member

Choose a reason for hiding this comment

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

😍 TIL

@webknjaz
Copy link
Member

I've restarted failed CI jobs.

@@ -473,3 +473,32 @@ def test_cidr_merge(self):
subnets = ['1.12.1.1', '1.12.1.255']
self.assertEqual(cidr_merge(subnets), ['1.12.1.1/32', '1.12.1.255/32'])
self.assertEqual(cidr_merge(subnets, 'span'), '1.12.1.0/24')

def test_ipsubnet(self):
Copy link
Member

Choose a reason for hiding this comment

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

where's negative tests?

self.assertEqual(ipsubnet(address, subnet), False)
address = '192.168.144.5'
subnet = '192.168.0.0/16'
self.assertEqual(ipsubnet(address), '192.168.144.5/32')
Copy link
Member

Choose a reason for hiding this comment

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

Please keep your code style consistent: first you assign vars for some time, then you pass lots of function args directly. There's a better way.

v = ipaddr(query, 'subnet')
query = netaddr.IPNetwork(v)
except NETADDR_ERRORS as err:
raise errors.AnsibleFilterError(err)
Copy link
Member

Choose a reason for hiding this comment

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

This is minor, but I like to keep exception chain when re-raising new ones. This is how you do it in forward-compatible manner:

from ansible.module_utils import six
...
	except NETADDR_ERRORS as err:
            six.raise_from(errors.AnsibleFilterError(err), err)

Copy link
Member

@webknjaz webknjaz left a comment

Choose a reason for hiding this comment

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

👍 🎉

@pierremahot
Copy link
Contributor Author

I have a problème with the exception :( doesn't look to work it return 1

- hosts: localhost
  tasks:
    - debug:
        msg: "{{ '192.168.1.1/24' | ipsubnet('-5') }}"

result

ansible-playbook test.yml
 [WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'


PLAY [localhost] *****************************************************************************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************************************************************************
ok: [localhost]

TASK [debug] *********************************************************************************************************************************************************************************
ok: [localhost] => {
    "msg": "1"
}

PLAY RECAP ***********************************************************************************************************************************************************************************
localhost                  : ok=2    changed=0    unreachable=0    failed=0

@webknjaz
Copy link
Member

@pierremahot can you add that to integration tests?

@webknjaz
Copy link
Member

Also, what do you mean by exception?

self._test_ipsubnet(args, res)

def _test_ipsubnet(self, ipsubnet_args, expected_result):
self.assertEqual(ipsubnet(*ipsubnet_args), expected_result)
Copy link
Member

Choose a reason for hiding this comment

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

I'd also add some test cases with assertRaises

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think

vtype = ipaddr(query, 'type')
        if vtype == 'address':
            v = ipaddr(query, 'cidr')
        elif vtype == 'network':
            v = ipaddr(query, 'subnet')
        else:
            return False

Can't raise error on :

query = netaddr.IPNetwork(v)
for i, subnet in enumerate(query.subnet(value.prefixlen), 1)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

i don't find the case

Copy link
Member

Choose a reason for hiding this comment

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

You could mock.patch it to do so :)

Copy link
Member

Choose a reason for hiding this comment

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

Also, look out for netaddr docs for the cases when it'll raise the exception.

@pierremahot
Copy link
Contributor Author

I can't have assertion error because of the way ipaddr works

expected = 'An Exception indicating a failure to convert between address types or notations.'
with self.assertRaises(AddrConversionError) as exc:
ipsubnet('192.168.144.5', '192.168.8.1.5')
self.assertEqual(exc.exception.message, expected)
Copy link
Member

Choose a reason for hiding this comment

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

This needs to go outside of CM. If ipsubnet will raise exception it will interrupt code block within with.

self.assertEqual(ipsubnet(*ipsubnet_args), expected_result)

expected = 'An Exception indicating a network address is not correctly formatted.'
with self.assertRaises(AddrConversionError) as exc:
Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I don't find case to raise an error

@pierremahot
Copy link
Contributor Author

I think ipaddr is the function that can raise error but it filter already the input of netaddr i don't find case where it raise an error

@webknjaz
Copy link
Member

Yeah, you're right: ipaddr brutally swallows all exceptions.

@ansibot ansibot added needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI. stale_ci This PR has been tested by CI more than one week ago. Close and re-open this PR to get it retested. labels Aug 14, 2018
@ansibot ansibot removed needs_ci This PR requires CI testing to be performed. Please close and re-open this PR to trigger CI. stale_ci This PR has been tested by CI more than one week ago. Close and re-open this PR to get it retested. labels Aug 14, 2018
@@ -541,3 +537,12 @@ def test_ipsubnet(self):

def _test_ipsubnet(self, ipsubnet_args, expected_result):
self.assertEqual(ipsubnet(*ipsubnet_args), expected_result)
expected = 'You must pass a valid subnet or IP address; invalid_subnet is invalid'
with self.assertRaises(AnsibleFilterError) as exc:
Copy link
Member

Choose a reason for hiding this comment

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

Please use assertRaisesRegexp instead.

@ansibot ansibot added the stale_ci This PR has been tested by CI more than one week ago. Close and re-open this PR to get it retested. label Aug 22, 2018
@gundalow gundalow merged commit d11e078 into ansible:devel Aug 30, 2018
@ansible ansible locked and limited conversation to collaborators Jul 22, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
affects_2.6 This issue/PR affects Ansible v2.6 docs This issue/PR relates to or includes documentation. feature This issue/PR relates to a feature request. needs_revision This PR fails CI tests or a maintainer has requested a review/revision of the PR. networking Network category stale_ci This PR has been tested by CI more than one week ago. Close and re-open this PR to get it retested. support:core This issue/PR relates to code supported by the Ansible Engineering Team. test This PR relates to tests.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants