Skip to content

Commit

Permalink
Teach CCCL about TCP addresses and other fixes
Browse files Browse the repository at this point in the history
CCCL needs to know about this to enable CC to create CIDR whitelists.

Fixes: #145

* Fixed a lot of spelling errors
* Fixed perf tests hardcoded partition
* Fixed perf tests spurious comma
* Added TCP support to LTM policies
* Added params to tox file to make running easier
* Fix a lot of Py3 errors in unit tests
  • Loading branch information
caphrim007 committed Aug 28, 2018
1 parent 39835eb commit 118a622
Show file tree
Hide file tree
Showing 26 changed files with 170 additions and 75 deletions.
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,9 @@ ENV/

# Rope project settings
.ropeproject

# symbols file for pytest
symbols.yaml

# PyCharm directory
.idea
4 changes: 3 additions & 1 deletion .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ disable=locally-disabled,
too-few-public-methods,
too-many-instance-attributes,
too-many-arguments,
too-many-public-methods
too-many-public-methods,
too-many-branches,
useless-object-inheritance


[REPORTS]
Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@ Creating issues is good, creating good issues is even better. Please provide:

# Contributing

This project is used internally by other F5 projects; we're not yet ready to accept contributions. Please check back later or see if another project, such as https://github.com/F5Networks/f5-common-python would be a good place for your contribution.
This project is used internally by other F5 projects; we're not yet ready to accept contributions.
Please check back later or see if another project, such as https://github.com/F5Networks/f5-common-python
would be a good place for your contribution.

# Copyright
Copyright (c) 2017,2018, F5 Networks, Inc.
Expand Down
2 changes: 1 addition & 1 deletion f5_cccl/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ class F5CcclValidationError(F5CcclError):
def __init__(self, msg):
"""Initialize with base config does not match schema message."""
super(F5CcclValidationError, self).__init__(msg)
self.msg = 'Service congifuration provided does not match schema: ' + \
self.msg = 'Service configuration provided does not match schema: ' + \
msg


Expand Down
2 changes: 1 addition & 1 deletion f5_cccl/resource/ltm/app_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def __eq__(self, other):
return False

for key in self._data:
if key == "variables" or key == "tables":
if key in ["variables", "tables"]:
# already compared
continue
if self._data[key] != other.data.get(key, None):
Expand Down
2 changes: 1 addition & 1 deletion f5_cccl/resource/ltm/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(self, name, partition, **properties):
super(Node, self).__init__(name, partition, **properties)

for key, value in self.properties.items():
if key == "name" or key == "partition":
if key in ["name", "partition"]:
continue

self._data[key] = properties.get(key, value)
Expand Down
26 changes: 24 additions & 2 deletions f5_cccl/resource/ltm/policy/condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class Condition(Resource):
"endsWith": None,
"startsWith": None,
"contains": None,
"matches": None,

"not": None,
"missing": None,
Expand All @@ -55,6 +56,9 @@ class Condition(Resource):
"httpHeader": False,
"httpCookie": False,

"tcp": True,
"address": False,

"tmName": None,
"values": list()
}
Expand All @@ -66,7 +70,6 @@ def __init__(self, name, data):

values = sorted(data.get('values', list()))
tm_name = data.get('tmName', None)
condition_map = dict()

# Does this rule match the HTTP hostname?
if data.get('httpHost', False):
Expand Down Expand Up @@ -99,6 +102,23 @@ def __init__(self, name, data):
condition_map = {
'httpCookie': True, 'tmName': tm_name, 'values': values}

# Does this rule match a TCP related setting?
elif data.get('tcp', False):
condition_map = {'tcp': True, 'values': values}

if data.get('external', False):
condition_map['external'] = True
elif data.get('internal', False):
condition_map['internal'] = True

if data.get('matches', False):
condition_map['matches'] = True

if data.get('address', False):
condition_map['address'] = True
else:
raise ValueError("must specify address for TCP matching "
"condition")
else:
# This class does not support the condition type; however,
# we want to create in order to manage the policy.
Expand All @@ -111,7 +131,9 @@ def __init__(self, name, data):
# For example, having a comparison option set to 'None' will conflict
# with the one that is set to 'True'
match_options = ['not', 'missing', 'caseSensitive']
comparisons = ['contains', 'equals', 'startsWith', 'endsWith']
comparisons = [
'contains', 'equals', 'startsWith', 'endsWith', 'matches'
]
for key in match_options + comparisons:
value = data.get(key, None)
if value:
Expand Down
4 changes: 2 additions & 2 deletions f5_cccl/resource/ltm/policy/policy.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ def __eq__(self, other):
"""Check the equality of the two objects.
Only compare the properties as defined in the
properties class dictionany.
properties class dictionary.
"""
if not isinstance(other, Policy):
return False
Expand Down Expand Up @@ -155,7 +155,7 @@ def _flatten_policy(self, data):
if 'items' in rulesReference:
policy['rules'] = self._flatten_rules(
rulesReference['items'])
elif key == 'name' or key == 'partition':
elif key in ['name', 'partition']:
pass
else:
policy[key] = data.get(key)
Expand Down
2 changes: 1 addition & 1 deletion f5_cccl/resource/ltm/policy/rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ def __eq__(self, other):
return False

for key in self.properties:
if key == 'actions' or key == 'conditions':
if key in ['actions', 'conditions']:
if len(self._data[key]) != len(other.data[key]):
return False
for index, obj in enumerate(self._data[key]):
Expand Down
39 changes: 37 additions & 2 deletions f5_cccl/resource/ltm/policy/test/test_condition.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,12 @@
'tmName': "Host",
'contains': True,
'values': ["www.acme.com"]
},
'tcp_address': {
'tcp': True,
'address': True,
'matches': True,
'values': ["10.10.10.10/32", "10.0.0.0/16"]
}
}

Expand Down Expand Up @@ -180,13 +186,13 @@ def test_create_http_uri_unsupported_match():
name="0"

with pytest.raises(ValueError):
condition = Condition(name, conditions['http_uri_unsupported'])
Condition(name, conditions['http_uri_unsupported'])


def test_create_http_unsupported_operand_type():
name="0"
with pytest.raises(ValueError):
condition = Condition(name, conditions['http_unsupported_operand_type'])
Condition(name, conditions['http_unsupported_operand_type'])


def test_create_http_uri_path_segment_match():
Expand Down Expand Up @@ -350,3 +356,32 @@ def test_uri_path(bigip):

with pytest.raises(NotImplementedError):
condition._uri_path(bigip)


def test_create_tcp_address_match():
name="0"
condition = Condition(name, conditions['tcp_address'])
data = condition.data

assert condition.name == "0"
assert not condition.partition

assert data.get('tcp')
assert data.get('values') == ["10.0.0.0/16", "10.10.10.10/32"]

assert 'httpHost' not in data
assert 'httpUri' not in data
assert 'httpCookie' not in data

assert not data.get('equals')
assert not data.get('startsWith')
assert not data.get('endsWith')
assert data.get('matches')

assert not data.get('missing')
assert not data.get('not')
assert not data.get('caseSensitive')

assert not data.get('index')
assert not data.get('path')
assert not data.get('pathSegment')
6 changes: 3 additions & 3 deletions f5_cccl/resource/ltm/pool.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ def __init__(self, name, partition, members=None, **properties):
super(Pool, self).__init__(name, partition, **properties)

for key, value in self.properties.items():
if key == "name" or key == "partition":
if key in ["name", "partition"]:
continue
self._data[key] = properties.get(key, value)

Expand All @@ -65,7 +65,7 @@ def __eq__(self, other):
return False

for key in self.properties:
if key == 'membersReference' or key == 'monitor':
if key in ['membersReference', 'monitor']:
continue

if isinstance(self._data[key], list):
Expand Down Expand Up @@ -112,7 +112,7 @@ def __init__(self, name, partition, default_route_domain, **properties):
"""Parse the CCCL schema input."""
pool_config = dict()
for k, v in properties.items():
if k == "members" or k == "monitors":
if k in ["members", "monitors"]:
continue
pool_config[k] = v

Expand Down
2 changes: 1 addition & 1 deletion f5_cccl/resource/ltm/pool_member.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def __init__(self, name, partition, pool=None, **properties):

self._pool = pool
for key, value in self.properties.items():
if key == 'name' or key == 'partition':
if key in ['name', 'partition']:
continue
self._data[key] = properties.get(key, value)

Expand Down
3 changes: 1 addition & 2 deletions f5_cccl/resource/ltm/virtual.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,8 +191,7 @@ def __eq__(self, other):
len(self._data[key]) != len(other.data.get(key, list())):
return False

if key == 'vlans' or key == 'policies' or key == 'rules' \
or key == 'metadata':
if key in ['vlans', 'policies', 'rules', 'metadata']:
if sorted(self._data[key]) != \
sorted(other.data.get(key, list())):
return False
Expand Down
2 changes: 1 addition & 1 deletion f5_cccl/resource/net/arp.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ def __init__(self, name, partition, **data):
super(Arp, self).__init__(name, partition)

for key, value in self.properties.items():
if key == "name" or key == "partition":
if key in ["name", "partition"]:
continue
self._data[key] = data.get(key, value)

Expand Down
7 changes: 3 additions & 4 deletions f5_cccl/resource/net/fdb/tunnel.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,9 @@ def __eq__(self, other):
for record in self._data[key]:
if record not in other.data[key]:
return False
else:
idx = other.data[key].index(record)
if record != other.data[key][idx]:
return False
idx = other.data[key].index(record)
if record != other.data[key][idx]:
return False
continue
if self._data[key] != other.data.get(key):
return False
Expand Down
2 changes: 1 addition & 1 deletion f5_cccl/resource/resource.py
Original file line number Diff line number Diff line change
Expand Up @@ -378,7 +378,7 @@ def _handle_http_error(self, error):
raise cccl_exc.F5CcclResourceNotFoundError(str(error))
elif code == 409:
raise cccl_exc.F5CcclResourceConflictError(str(error))
elif code >= 400 and code < 500:
elif 400 <= code < 500:
raise cccl_exc.F5CcclResourceRequestError(str(error))
else:
raise cccl_exc.F5CcclError(str(error))
Expand Down
21 changes: 18 additions & 3 deletions f5_cccl/schemas/cccl-ltm-api-schema.yml
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ definitions:
type: "object"
description: |
Defines an L7 Policy condition.
The supported operands are http-host, http-uri, http-header, and http-cookie
The supported operands are http-host, http-uri, http-header, http-cookie, and tcp
Operands
httpHost -- Provides all or part of the HTTP Host header, only match against
Expand All @@ -282,6 +282,8 @@ definitions:
to match against is required.
httpHeader -- Returns the value of a particular header. If matching a HTTP header,
tmName must be defined. A list of values to match against is required.
tcp -- Returns the value of a particular TCP related attribute. A list of values
to match against is required.
Supported match operators are equals, startsWith, endsWith, and contains. These match
operators can be negated by setting 'not' to true.
Expand All @@ -290,7 +292,7 @@ definitions:
# Match modifiers
caseSensitive:
default: false
description: "The match string is case-sensive"
description: "The match string is case-sensitive"
type: "boolean"
missing:
default: false
Expand All @@ -314,6 +316,9 @@ definitions:
contains:
description: "The value matches if it contains a certain string."
type: "boolean"
matches:
description: "The value matches if it matches certain values."
type: "boolean"

# Operand types
httpUri:
Expand All @@ -328,6 +333,9 @@ definitions:
httpHeader:
description: "Name of the HTTP header that contains the value to compare."
type: "boolean"
tcp:
description: "Inspect and match on various TCP properties of a connection."
type: "boolean"

# Operand values
tmName:
Expand All @@ -354,6 +362,9 @@ definitions:
index:
description: "The specified index is used to match a specific segment."
type: "integer"
address:
description: "Match on IP address"
type: "boolean"

oneOf:
- required:
Expand Down Expand Up @@ -385,6 +396,10 @@ definitions:
- "httpUri"
- "host"
- "values"
- required:
- "tcp"
- "address"
- "values"

l7PolicyType:
id: "#/definitions/l7PolicyType"
Expand Down Expand Up @@ -545,7 +560,7 @@ definitions:
type: "string"
loadBalancingMode:
default: "round-robin"
description: "Loadbalancing algorithm to use on pool."
description: "Load balancing algorithm to use on pool."
enum:
- "dynamic-ratio-member"
- "dynamic-ratio-node"
Expand Down
20 changes: 20 additions & 0 deletions f5_cccl/schemas/tests/ltm_service.json
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,26 @@
"location": "http://www.othersite.com"
}
]
},
{
"conditions": [
{
"tcp": true,
"address": true,
"matches": true,
"external": true,
"values": ["10.10.10.10/32"]
}
],
"name": "rule3",
"actions": [
{
"request": true,
"redirect": true,
"httpReply": true,
"location": "http://www.othersite.com"
}
]
}
]
}],
Expand Down
Loading

0 comments on commit 118a622

Please sign in to comment.