vstc 1.0.1
SpirentTestCenter API
License: MIT
Requires Python: >=3.6

# Changelist
* 1.0.1,  update README
* 1.0.0,  first release

# Feedback

give your feedback by following ways<br/>

* visit https://github.com/dvdface/vstc (preferred)

* send email to dvdface@gmail.com
  

# How to install

`pip install vstc`

# Known issues

1.x version, only support call locally. AKA, you can share a physical STC port between many test runers in the same PC. <br/>

It will support RPC in 2.x version so that you can share a physical STC port between many PCs. <br/>

# Overview
VSTC means Virtual Spirent TestCenter.<br/>

It takes Spirent TestCenter and Switch as a Virutal Spirent TestCenter.<br/>

![image](https://github.com/dvdface/vstc/raw/master/res/Virtual%20Spirent%20TestCenter.png)<br/>

In general, you connect chassis, reserve port, create device under port, then create raw/bound streamblock, start traffic.<br/>

With VSTC, it takes a STC port and Vlan ID as a virtual STC port so that the 
traffics generated by a same STC port can be redirect to target port by switch with VLAN ID.<br/>

Aka, one STC port can be used as  multiple virutal ports by this way. so, you can save your money to buy Spirent TestCenter<br/>

Of course, you can use VSTC library without vid, so VSTC is just a ordinary STC.<br/>

# Class diagram

![image](https://github.com/dvdface/vstc/raw/master/res/VSTC%20Class%20Diagram.png)<br/>

The relationship between classes is Just like Spirent TestCenter GUI.<br/>

Chassis class provides ability to reserve port, create multicast groups.<br/>

Port class provides ability to create devices, streamblocks.<br/>

MCGroup represents Multicast Group, you can add IGMP type device into it.<br/>

StreamBlock represents streamblock under Port.<br/>

Device represents Device under Port.<br/>

# Impelmentation explains

Under the hook, VSTC uses stc:: / sth:: api provided by Spirent TestCenter.

VSTC uses STCObject to wrap object handle in stc::/sth::, so you can access / set object's attributes by PortObject['attribute'] syntax.<br/>

In STCObject, it provides properties by which you can access attributes more easily.<br/>

# QA Info
```
Name                 Stmts   Miss  Cover
----------------------------------------
vstc\__init__.py         9      0   100%
vstc\testcenter.py     821    116    86%
vstc\util.py            70      6    91%
----------------------------------------
TOTAL                  900    122    86%
```

# How to use
* **Chassis class<br/>**
   
   ```
   # connect testcenter with ip 10.182.32.138 , without reserving any port
   # creating chassis class will auto connect chassis
   chassis = Chassis('10.182.32.138')

   # disconnect chassis
   chassis.disconnect()

   # apply changes ( it will apply automatically)
   chassis.apply()

   # connect testcenter and reserve port
   chassis = Chassis('10.182.32.138', [{ 'location' : '//10.182.32.138/1/1', 'vid': None}, { 'location' : '//10.182.32.138/1/2', 'vid': None}])

   # connect testcenter, reserve port and specify a default vlan with specified vid
   # when you create a device under the port, it will insert a vlan layer with vid 100 for you
   # when you create a streamblock, it will insert a vlan layer with vid 100 for you too
   chassis = Chassis('10.182.32.138', [{ 'location' : '//10.182.32.138/1/1', 'vid': 100}])

   # save xml file
   chassis.save('test_configuration.xml')

   # get chassis serial number
   chassis.serial

   # get chassis ip
   chassis.ip
   ```

* **Port class<br/>**
   ```
   # access reserved ports
   # access all reserved ports
   # it wil return List[Port]
   chassis.ports

   # access the first reserved ports
   port = chassis.ports[0]
   
   # start capture 
   port.start_capture()
   # stop capture
   port.stop_capture()
   # save capture
   port.save_capture('demo.pcap')

   # change DataPathMode
   # normal: send / receive data to other port 
   port.mode = PortMode.normal
   # loopback: send data to self
   port.mode = PortMode.local_loopback

   # get port location
   # //10.182.32.138/1/1
   port.location
   # get port vid
   port.vid

   # get port ownership state
   # OwnershipState.RESERVED銆丄VAILABLE銆丏ISCONNECTED
   port.ownership

   # get port owner host
   port.owner_host == socket.gethostname()
   
   # get port onwer user
   port.owner_user
   ```
* **MCGroup class<br/>**
   ```
   # access multicast groups under chassis
   # it will return List[MCGroup]
   chassis.mcgroups

   # create a multicast group under chassis with name 'ipv4group', and group ip '224.0.0.1'
   mcgroup = chassis.create_mcgroup('ipv4group', ip='224.0.0.1')

   # remove mcgroup from chassis
   chassis.remove_mcgroup(mcgroup)

   # get / set group name
   mcgroup.name
   mcgroup.name = 'demo name'

   # get / set group ip
   mcgroup.ip
   mcgroup.ip = '225.0.0.1'

   # add IGMP device into MCGroup
   # 1.get a reserved port under chassis
   port = chassis.ports[0]
   # 2. create igmp device under port
   igmpDevice = port.create_device(DeviceType.igmp)
   # 3. add igmp device into a mcgroup
   mcgroup.add(igmpDevice)
   # 4. remove igmp device from mcgroup
   mcgroup.remove(igmpDevice)
   
   # get devices in the mcgroup
   # it will return List[IGMPDevice]
   mcgroup.devices
   ```

* **Device class<br/>**
   ```
   # create traffic only device
   dev = port.create_device(DeviceType.host)

   # set device mac, ip, mask, gateway, gateway mac
   dev.mac = '00:10:94:00:01:01'
   dev.ip = '192.168.1.1'
   dev.mask = 24
   dev.gateway = '192.168.1.254'
   dev.gwmac = '00:10:94:00:11:11'

   # get stats for device
   dev.stats

   # create dhcp devices
   
   dhcpv4server = port.create_device(DeviceType.dhcpv4_server, ip_address='192.168.10.1', ipaddress_pool='192.168.10.2')
   dhcpv4client = port.create_device(DeviceType.dhcpv4_client)

   # create igmp devices
   igmp = port.create_device(DeviceType.igmp)

   # create pppox devices
   pppoxserver = port.create_device(DeviceType.pppox_server)
   pppoxclient = port.create_device(DeviceType.pppox_client)

   # set pppox auth mode, username, password and so on
   pppoxserver.auth = PPPoXAuthMode.auto
   pppoxserver.username = 'test'
   pppoxserver.password = 'testpassword'
   ```

* **Streamblock class<br/>**
   ```
   # raw streamblock
   streamblock = port.create_streamblock(StreamType.raw)

   # bound streamblock
   client = port.create_device(DeviceType.dhcpv4_client)
   server = port.create_device(DeviceType.dhcpv4_server, ip_address='192.168.1.254', ipaddress_pool='192.168.1.1')

   streamblock = port.create_streamblock(StreamType.bound, src_handle=client.handle, dst_handle=server.handle)

   # start / stop streamblock
   streamblock.start()

   wait_for_true(lambda : streamblock.state == StreamState.running)

   streamblock.stop()
   
   # get / set streamblock speed
   streamblock.speed
   streamblock.speed = '10mbps'
   streamblock.speed = '10pps'
   streamblock.speed = '10%'

   # get / set frame length
   streamblock.length_mode
   streamblock.min_length
   streamblock.max_length
   streamblock.length_step
   streamblock.length_mode = LengthMode.incr
   streamblock.min_length = 100
   streamblock.max_length = 1500
   streamblock.length_step = 64

   # get / set fill type and fill constant
   streamblock.fill_type
   streamblock.fill_const
   streamblock.fill_type = FillType.constant
   streamblock.fill_const = 0x1234
   ```

# How to extend
## Extend Device
```
class NewDevice(Device):

   def _create_device(self, **kwargs) -> STCObject:
      # create device here by using stc:: or sth:: api
      dev_ret = SpirentAPI.instance.sth_emulation_device_config(mode='create', port_handle=self.port.handle, **kwargs)

      # wrap device object hanlde with STCObject
      return STCObject(dev_ret.handle)
```