Add initial support for iperf3, both in client and server modes.
authorAdam Israel <adam.israel@canonical.com>
Thu, 10 Nov 2016 16:20:46 +0000 (11:20 -0500)
committerAdam Israel <adam.israel@canonical.com>
Thu, 10 Nov 2016 16:20:46 +0000 (11:20 -0500)
Signed-off-by: Adam Israel <adam.israel@canonical.com>
layers/netutils/README.md
layers/netutils/actions.yaml
layers/netutils/actions/iperf [new file with mode: 0755]
layers/netutils/config.yaml [new file with mode: 0644]
layers/netutils/reactive/layer_netutils.py

index 525c2d7..e8258c0 100644 (file)
@@ -38,6 +38,16 @@ timing:
   enqueued: 2016-06-29 14:50:03 +0000 UTC
   started: 2016-06-29 14:50:03 +0000 UTC
 ```
+## iperf3
+
+Because iperf3 has a client and server component, the netutils charm can operate
+as both. Setting the iperf3 configuration value to True will start iperf3 in
+server mode, running as a daemon.
+```
+$ juju deploy cs:~nfv/netutils client
+$ juju deploy cs:~nfv/netutils server iperf3=True
+$ juju run-action client/0 iperf host=<ip of server> [...]
+```
 
 ## Scale out Usage
 
index 5c99e21..f4f7884 100644 (file)
@@ -44,4 +44,90 @@ dig:
       type: string
   required:
     - host
+iperf:
+    description: ""
+    params:
+      host:
+        description: ""
+        type: string
+      port:
+        description: ""
+        type: integer
+        default: 5201
+      format:
+        description: ""
+        type: string
+      interval:
+        description: ""
+        type: string
+      affinity:
+        description: ""
+        type: string
+      udp:
+        description: "Use UDP rather than TCP"
+        type: boolean
+        default: False
+      bandwidth:
+        description: "Set the target bandwidth to n bits/sec (default 1Mbit/sec for UDP, unlimited for TCP)"
+        type: integer
+        default: 1
+      time:
+        description: "Time, in seconds, to transmit for."
+        type: integer
+        default: 10
+      blockcount:
+        description: "The number of blocks to transmit"
+        type: integer
+      length:
+        description: "The length of buffer to read or write (default 128KB for TCP, 8KB for UDP)"
+        type: integer
+      parallel:
+        description: "The number of parallel client streams to run"
+        type: integer
+      reverse:
+        description: "Run in reverse mode (server sends, client receives)."
+        type: boolean
+        default: false
+      window:
+        description: "Window size/socket buffer size."
+        type: integer
+      bind:
+        description: "Bind to a specific interface or multicast address"
+        type: string
+      mss:
+        description: "Set the TCP maximum segment size (MTU - 40 bytes)"
+        type: integer
+      no-delay:
+        description: "Set the TCP no delay, disabling Nagle's algorithm."
+        type: boolean
+        default: false
+      ipv4:
+        description: "Only use IPv4"
+        type: boolean
+        default: false
+      ipv6:
+        description: "Only use IPv6"
+        type: boolean
+        default: false
+      tos:
+        description: "Set the IP 'type of service'"
+        type: integer
+      flowlabel:
+        description: "Set the IPv6 flow label (linux-only)"
+        type: string
+      zerocopy:
+        description: "Use a 'zero copy' method of sending data, such as sendfile(s), instead of the usual write(2)."
+        type: boolean
+        default: false
+      omit:
+        description: "Omit the first n seconds of the test, to skip past the TCP slow-start period."
+        type: integer
+      title:
+        description: "Prefix every output line with this string."
+        type: string
+      congestion:
+        description: "Set the linux congestion control algorithm."
+        type: string
 
+    required:
+      - host
diff --git a/layers/netutils/actions/iperf b/layers/netutils/actions/iperf
new file mode 100755 (executable)
index 0000000..750028e
--- /dev/null
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+import sys
+sys.path.append('lib')
+
+from charms.reactive import main
+from charms.reactive import set_state
+from charmhelpers.core.hookenv import action_fail
+
+"""
+`set_state` only works here because it's flushed to disk inside the `main()`
+loop. remove_state will need to be called inside the action method.
+"""
+set_state('actions.iperf3')
+
+try:
+    main()
+except Exception as e:
+    action_fail(repr(e))
diff --git a/layers/netutils/config.yaml b/layers/netutils/config.yaml
new file mode 100644 (file)
index 0000000..6101063
--- /dev/null
@@ -0,0 +1,5 @@
+options:
+    iperf3:
+        type: boolean
+        default: false
+        description: "Enabling this option will start iperf3 in server mode."
index 697e83c..1fd4cb2 100644 (file)
@@ -1,17 +1,20 @@
 from charmhelpers.core.hookenv import (
-    status_set,
     action_get,
-    action_set,
     action_fail,
+    action_set,
+    config,
+    log,
+    status_set,
 )
 
 from charms.reactive import (
+    remove_state as remove_flag,
+    set_state as set_flag,
     when,
     when_not,
-    set_state as set_flag,
-    remove_state as remove_flag,
 )
 import charms.sshproxy
+from subprocess import CalledProcessError
 
 
 @when_not('netutils.ready')
@@ -95,3 +98,40 @@ def traceroute():
         action_set({'output': result})
     finally:
         remove_flag('actions.traceroute')
+
+
+@when('actions.iperf3')
+def iperf3():
+    err = ''
+    try:
+        # TODO: read all the flags via action_get and build the
+        # proper command line to run iperf3
+        host = action_get('host')
+
+        cmd = 'iperf3 -c {} --json'.format(host)
+        result, err = charms.sshproxy._run(cmd)
+    except CalledProcessError as e:
+        action_fail('iperf3 command failed:' + e.output)
+    else:
+        action_set({'outout': result})
+    finally:
+        remove_flag('actions.iperf3')
+
+
+@when('config.changed')
+def config_changed():
+    """ Handle configuration changes """
+    cfg = config()
+    if cfg.changed('iperf3'):
+        if cfg['iperf3']:
+            # start iperf in server + daemon mode
+            cmd = "iperf3 -s -D"
+        else:
+            cmd = "killall iperf3"
+        try:
+            charms.sshproxy._run(cmd)
+            log("iperf3 stopped.")
+        except CalledProcessError:
+            log("iperf3 not running.")
+        else:
+            log("iperf3 started.")