Skip to content

Commit

Permalink
align with models 0.1.2
Browse files Browse the repository at this point in the history
  • Loading branch information
ajbalogh committed Jan 6, 2021
1 parent 4638613 commit d1deac0
Show file tree
Hide file tree
Showing 9 changed files with 172 additions and 27 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# ***snappi***
# snappi
Create test case scripts once using the `snappi` client and run them using a
traffic generator that conforms to the [Open Traffic Generator API](https://github.com/open-traffic-generator/models/releases).

## *Install the client*
## Install the client
```
pip install snappi
```

## *Start scripting*
## Start scripting
```python
"""A simple test that demonstrates the following:
- A port that transmits an ethernet/vlan/ipv4/tcp flow
Expand All @@ -31,7 +31,7 @@ flow.rate.pps = 1000
flow.duration.fixed_packets.packets = 10000
api.set_config(config)

tx_state = api.transmitstate(state='start')
tx_state = api.transmit_state(state='start')
api.set_transmit(tx_state)

while True:
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
0.1.1
0.1.2
48 changes: 45 additions & 3 deletions snappi/snappicommon.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def __init__(self):
self.base_url = 'http://127.0.0.1:80'
self._session = requests.Session()

def send_recv(self, method, relative_url, payload=None):
def send_recv(self, method, relative_url, payload=None, return_object=None):
url = '%s%s' % (self.base_url, relative_url)
data = None
if payload is not None:
Expand All @@ -26,7 +26,7 @@ def send_recv(self, method, relative_url, payload=None):
if response.ok is not True:
raise Exception(response.status_code)
elif response.headers['content-type'] == 'application/json':
return yaml.safe_load(response.text)
return return_object.deserialize(yaml.safe_load(response.text))
else:
return None

Expand Down Expand Up @@ -83,7 +83,7 @@ def deserialize(self, encoded_snappi_object):
Args
----
- encoded_snappi_object (Union[str, dict]): The object to deserialize.
The supported encodings of obj are json, yaml and python dict.
The supported encodings of str are json and yaml.
Returns
-------
Expand Down Expand Up @@ -133,6 +133,10 @@ def __str__(self):


class SnappiList(object):
JSON = 'json'
YAML = 'yaml'
DICT = 'dict'

"""Container class for SnappiObject
Inheriting classes contain 0..n instances of an OpenAPI components/schemas
Expand Down Expand Up @@ -201,8 +205,46 @@ def clear(self):
self._items.clear()
self._index = -1

def serialize(self, encoding=JSON):
if encoding == SnappiObject.JSON:
return json.dumps(self._encode(), indent=2)
elif encoding == SnappiObject.YAML:
return yaml.safe_dump(self._encode())
elif encoding == SnappiObject.DICT:
return self._encode()
else:
raise NotImplementedError('Encoding %s not supported' % encoding)

def _encode(self):
return [item._encode() for item in self._items]

def deserialize(self, encoded_snappi_list):
"""Deserialize a python list into the current snappi list.
If the input `encoded_snappi_list` does not match the current
snappi list an exception will be raised.
Args
----
- encoded_snappi_list (Union[str, list]): The object to deserialize.
The supported encodings of str are json and yaml.
Returns
-------
- obj(snappicommon.SnappiList): A snappi list object
"""
if isinstance(encoded_snappi_list, (str, unicode)):
encoded_snappi_list = yaml.safe_load(encoded_snappi_list)
self._decode(encoded_snappi_list)
return self

def _decode(self, encoded_snappi_list):
item_class_path = self.__class__.__module__.replace('list', '')
item_class_name = self.__class__.__name__.replace('List', '')
module = importlib.import_module(item_class_path)
object_class = getattr(module, item_class_name)
for item in encoded_snappi_list:
self._add(object_class()._decode(item))

def __str__(self):
return yaml.safe_dump(self._encode())
44 changes: 32 additions & 12 deletions snappi/snappigenerator.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ def _get_openapi_file(self):
self._openapi = yaml.safe_load(openapi_content)

def _generate(self):
self._write_paths()
self._write_api_class()
self._write_init()
return self

Expand All @@ -103,7 +103,7 @@ def _write_init(self):
with open(filename, 'w') as self._fid:
self._write(0, 'from .api import Api')

def _write_paths(self):
def _write_api_class(self):
api_filename = os.path.join(self._src_dir, 'api.py')
with open(api_filename, 'a') as self._fid:
self._write(0, 'from .snappicommon import SnappiRestTransport')
Expand All @@ -130,16 +130,20 @@ def _write_paths(self):
else:
content = ', content'
payload = ', payload=content'
response = parse('$..responses..schema').find(operation)
response_object = ''
if len(response) > 0:
object_name, property_name, class_name = self._get_object_property_class_names(response[0].value)
if property_name is not None:
response_object = ', return_object=self.%s()' % property_name
with open(api_filename, 'a') as self._fid:
self._write()
self._write(1, 'def %s(self%s):' % (method_name, content))
self._write(2, '"""%s %s' % (path['method'].upper(), path['url']))
self._write(0)
self._write(2, '%s' % self._get_description(operation))
self._write(2, '"""')
self._write(
2, "return self.send_recv('%s', '%s'%s)" %
(path['method'], path['url'], payload))
self._write(2, "return self.send_recv('%s', '%s'%s%s)" % (path['method'], path['url'], payload, response_object))

# write top level objects for requests
for yobject in self._openapi['paths'].values():
Expand All @@ -155,6 +159,12 @@ def _write_paths(self):
object_name = top_level_schema['$ref'].split('/')[-1]
property_name = object_name.lower().replace('.', '_')
class_name = object_name.replace('.', '')
schema_object = self._get_object_from_ref(top_level_schema['$ref'])
if schema_object['type'] == 'array':
class_name = self._write_snappi_list(schema_object['items']['$ref'], property_name)
self._write_snappi_object(schema_object['items']['$ref'])
else:
self._write_snappi_object(top_level_schema['$ref'])
with open(api_filename, 'a') as self._fid:
self._write()
self._write(1, 'def %s(self):' % property_name)
Expand All @@ -166,7 +176,17 @@ def _write_paths(self):
self._write(
2, "from .%s import %s" % (class_name.lower(), class_name))
self._write(2, "return %s()" % (class_name))
self._write_snappi_object(top_level_schema['$ref'])

def _get_object_property_class_names(self, schema):
object_name = None
property_name = None
class_name = None
if '$ref' in schema:
ref_name = schema['$ref']
object_name = ref_name.split('/')[-1]
property_name = object_name.lower().replace('.', '_')
class_name = object_name.replace('.', '')
return (object_name, property_name, class_name)

def _write_snappi_object(self, ref):
schema_object = self._get_object_from_ref(ref)
Expand Down Expand Up @@ -235,6 +255,7 @@ def _get_choice_names(self, schema_object):
def _process_properties(self, class_name=None, schema_object=None):
"""Process all properties of a /component/schema object
Write a factory method for all choice
If there are no properties then the schema_object is a primitive or array type
"""
refs = []
if 'properties' in schema_object:
Expand Down Expand Up @@ -295,7 +316,7 @@ def _write_snappi_list(self, ref, property_name):
# write factory method for the schema object in the list
self._write_factory_method(None, class_name.lower(), ref, True)
# write choice factory methods if any
if 'choice' in yobject['properties']:
if 'properties' in yobject and 'choice' in yobject['properties']:
for property in yobject['properties']:
if property not in yobject['properties']['choice'][
'enum']:
Expand All @@ -305,6 +326,7 @@ def _write_snappi_list(self, ref, property_name):
ref = yobject['properties'][property]['$ref']
self._write_factory_method(class_name, property, ref,
True)
return '%sList' % class_name

def _write_factory_method(self,
container_class_name,
Expand Down Expand Up @@ -373,8 +395,7 @@ def _write_snappi_property(self, schema_object, name, property):
self._write(1, 'def %s(self):' % name)
self._write(2, '"""%s getter' % (name))
self._write()
for line in self._get_description(property):
self._write(2, line)
self._write(2, self._get_description(property))
self._write()
self._write(2, 'Returns: %s' % self._get_type_restriction(property))
self._write(2, '"""')
Expand All @@ -385,8 +406,7 @@ def _write_snappi_property(self, schema_object, name, property):
self._write(1, 'def %s(self, value):' % name)
self._write(2, '"""%s setter' % (name))
self._write()
for line in self._get_description(property):
self._write(2, line)
self._write(2, self._get_description(property))
self._write()
self._write(2, 'value: %s' % self._get_type_restriction(property))
self._write(2, '"""')
Expand Down Expand Up @@ -823,5 +843,5 @@ def _get_inline_ref(self, base_dir, filename, inline_key):

if __name__ == '__main__':
openapi_filename = None
openapi_filename = os.path.normpath('../../models/openapi.yaml')
# openapi_filename = os.path.normpath('../../models/openapi.yaml')
SnappiGenerator(dependencies=False, openapi_filename=openapi_filename)
13 changes: 13 additions & 0 deletions snappi/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ def get_config():
status=200)


@app.route('/results/port', methods=['POST'])
def get_port_metrics():
import snappi
api = snappi.api.Api()
port_metrics_request = api.port_metrics_request()
port_metrics_request.deserialize(request.data.decode('utf-8'))
port_metrics = api.port_metrics()
port_metrics.portmetric().portmetric()
return Response(port_metrics.serialize(),
mimetype='application/json',
status=200)


@app.after_request
def after_request(resp):
print(request.method, request.url, ' -> ', resp.status)
Expand Down
47 changes: 47 additions & 0 deletions snappi/tests/test_e2e_port_flow_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import pytest


def test_e2e_port_flow_config():
"""Get port metrics
"""
import snappi

api = snappi.api.Api()

config = api.config()

tx_port, rx_port = config.ports \
.port(name='Tx Port', location='10.36.74.26;02;13') \
.port(name='Rx Port', location='10.36.74.26;02;14')

flow = config.flows.flow(name='Tx -> Rx Flow')
flow.tx_rx.port.tx_name = tx_port.name
flow.tx_rx.port.rx_name = rx_port.name
flow.size.fixed = 128
flow.rate.pps = 1000
flow.duration.fixed_packets.packets = 10000

eth, vlan, ip, tcp = flow.packet.ethernet().vlan().ipv4().tcp()

eth.src.value = '00:00:01:00:00:01'
eth.dst.value_list = ['00:00:02:00:00:01', '00:00:02:00:00:01']
eth.dst.result_group = 'eth dst mac'

ip.src.increment.start = '1.1.1.1'
ip.src.increment.step = '0.0.0.1'
ip.src.increment.count = 10

ip.dst.decrement.start = '1.1.2.200'
ip.dst.decrement.step = '0.0.0.1'
ip.dst.decrement.count = 10

ip.priority.dscp.phb.value_list = [8, 16, 32]
ip.priority.dscp.ecn.value = 1
tcp.src_port.increment.start = '10'
tcp.dst_port.increment.start = 1

print(config)


if __name__ == '__main__':
pytest.main(['-vv', '-s', __file__])
13 changes: 13 additions & 0 deletions snappi/tests/test_port_metrics.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import pytest


def test_port_metrics(api):
"""Get port metrics
"""
request = api.port_metrics_request()
port_metrics = api.get_port_metrics(request)
print(port_metrics)


if __name__ == '__main__':
pytest.main(['-vv', '-s', __file__])
15 changes: 15 additions & 0 deletions snappi/tests/test_snappi_list_serialization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import pytest


def test_snappi_list_serialization(api):
"""Test serialization and deserialization of Snappi list objects
"""
port_metrics1 = api.port_metrics().portmetric().portmetric()
serialization = port_metrics1.serialize()
port_metrics2 = api.port_metrics()
port_metrics2.deserialize(serialization)
print(port_metrics2)


if __name__ == '__main__':
pytest.main(['-vv', '-s', __file__])
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@


@pytest.mark.parametrize('encoding', ['yaml', 'json', 'dict'])
def test_serdes(api, b2b_config, encoding):
"""Demonstrate serialization and deserialization of Snappi objects
def test_snappi_object_serialization(api, b2b_config, encoding):
"""Test serialization and deserialization of Snappi objects
"""
# serialize the configuration locally
serialization1 = b2b_config.serialize(encoding)
Expand All @@ -14,11 +14,6 @@ def test_serdes(api, b2b_config, encoding):
# use a mock web server to pull the config
config = api.get_config()

# TBD
# the following step should be removed when the code generation of
# responses is complete
config = api.config().deserialize(config)

# serialize the pulled config
serialization2 = config.serialize(encoding)

Expand Down

0 comments on commit d1deac0

Please sign in to comment.