~jocave/checkbox/hybrid-amd-gpu-mods

« back to all changes in this revision

Viewing changes to checkbox-old/scripts/network

  • Committer: Tarmac
  • Author(s): Brendan Donegan
  • Date: 2013-06-03 11:12:58 UTC
  • mfrom: (2154.2.1 bug1185759)
  • Revision ID: tarmac-20130603111258-1b3m5ydvkf1accts
"[r=zkrynicki][bug=1185759][author=brendan-donegan] automatic merge by tarmac"

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#!/usr/bin/env python3
 
2
"""
 
3
Copyright (C) 2012 Canonical Ltd.
 
4
 
 
5
Authors
 
6
  Jeff Marcom <jeff.marcom@canonical.com>
 
7
 
 
8
This program is free software: you can redistribute it and/or modify
 
9
it under the terms of the GNU General Public License version 3,
 
10
as published by the Free Software Foundation.
 
11
 
 
12
This program is distributed in the hope that it will be useful,
 
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
 
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
15
GNU General Public License for more details.
 
16
 
 
17
You should have received a copy of the GNU General Public License
 
18
along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
19
"""
 
20
 
 
21
from argparse import ArgumentParser
 
22
import configparser
 
23
import fcntl
 
24
import ftplib
 
25
from ftplib import FTP
 
26
import logging
 
27
import os
 
28
import shlex
 
29
import socket
 
30
import struct
 
31
from subprocess import check_call, CalledProcessError
 
32
import sys
 
33
import threading
 
34
import time
 
35
 
 
36
logging.basicConfig(level=logging.DEBUG)
 
37
 
 
38
 
 
39
class FTPPerformanceTest(object):
 
40
    """
 
41
    Provides file transfer rate based information while
 
42
    using the FTP protocol and sending a file (DEFAULT=1GB)
 
43
    over the local or public network using a specified network
 
44
    interface on the host.
 
45
    """
 
46
 
 
47
    def __init__(
 
48
            self,
 
49
            interface,
 
50
            target,
 
51
            username="anonymous",
 
52
            password="ftp"):
 
53
 
 
54
        self.iface = Interface(interface)
 
55
        self.target = target
 
56
        self.username = username
 
57
        self.password = password
 
58
 
 
59
        self.binary_size = 1
 
60
 
 
61
        self.file2send = "ftp_performance_test"
 
62
 
 
63
    def _make_file2send(self):
 
64
        """
 
65
        Makes binary file to send over FTP.
 
66
        Size defaults to 1GB if not supplied.
 
67
        """
 
68
 
 
69
        logging.debug("Creating %sGB file", self.binary_size)
 
70
 
 
71
        file_size = (1024 * 1024 * 1024) * self.binary_size
 
72
        with open(self.file2send, "wb") as out:
 
73
            out.seek((file_size) - 1)
 
74
            out.write('\0'.encode())
 
75
 
 
76
    def send_file(self, filename=None):
 
77
        """
 
78
        Sends file over the network using FTP and returns the
 
79
        amount of bytes sent and delay between send and completed.
 
80
        """
 
81
 
 
82
        if filename is None:
 
83
            file = open(self.file2send, 'rb')
 
84
            filename = self.file2send
 
85
 
 
86
        send_time = time.time()
 
87
 
 
88
        try:
 
89
            logging.debug("Sending file")
 
90
            self.remote.storbinary("STOR " + filename, file, 1024)
 
91
        except ftplib.all_errors as send_failure:
 
92
            logging.error("Failed to send file to %s", self.target)
 
93
            logging.error("Reason: %s", send_failure)
 
94
            return 0, 0
 
95
 
 
96
        file.close()
 
97
 
 
98
        time_lapse = time.time() - send_time
 
99
        bytes_sent = os.stat(filename).st_size
 
100
 
 
101
        return bytes_sent, time_lapse
 
102
 
 
103
    def close_connection(self):
 
104
        """
 
105
        Close connection to remote FTP target
 
106
        """
 
107
        self.remote.close()
 
108
 
 
109
    def connect(self):
 
110
        """
 
111
        Connects to FTP target and set the current directory as /
 
112
        """
 
113
 
 
114
        logging.debug("Connecting to %s", self.target)
 
115
        try:
 
116
            self.remote = FTP(self.target)
 
117
            self.remote.set_debuglevel(2)
 
118
            self.remote.set_pasv(True)
 
119
        except socket.error as connect_exception:
 
120
            logging.error("Failed to connect to: %s", self.target)
 
121
            return False
 
122
 
 
123
        logging.debug("Logging in")
 
124
        logging.debug("{USER:%s, PASS:%s}", self.username, self.password)
 
125
 
 
126
        try:
 
127
            self.remote.login(self.username, self.password)
 
128
        except ftplib.error_perm as login_exception:
 
129
            logging.error("failed to log into target: %s", self.target)
 
130
            return False
 
131
 
 
132
        default_out_dir = ""
 
133
        self.remote.cwd(default_out_dir)
 
134
        return True
 
135
 
 
136
    def run(self):
 
137
 
 
138
        info = {
 
139
            "Interface": self.iface.interface,
 
140
            "HWAddress": self.iface.macaddress,
 
141
            "Duplex": self.iface.duplex_mode,
 
142
            "Speed": self.iface.max_speed,
 
143
            "Status": self.iface.status
 
144
        }
 
145
 
 
146
        logging.debug(info)
 
147
 
 
148
        if not os.path.isfile(self.file2send):
 
149
            self._make_file2send()
 
150
 
 
151
        # Connect to FTP target and send file
 
152
        connected = self.connect()
 
153
        filesize, delay = self.send_file()
 
154
 
 
155
        # Remove created binary
 
156
        try:
 
157
            os.remove(self.file2send)
 
158
        except (IOError, OSError) as file_delete_error:
 
159
            logging.error("Could not remove previous ftp file")
 
160
            logging.error(file_delete_error)
 
161
 
 
162
        if connected and filesize > 0:
 
163
 
 
164
            logging.debug("Bytes sent (%s): %.2f seconds", filesize, delay)
 
165
 
 
166
            # Calculate transfer rate and determine pass/fail status
 
167
            mbs_speed = float(filesize / 131072) / float(delay)
 
168
            percent = (mbs_speed / int(info["Speed"])) * 100
 
169
            logging.debug("Transfer speed:")
 
170
            logging.debug("%3.2f%% of", percent)
 
171
            logging.debug("theoretical max %smbs", int(info["Speed"]))
 
172
 
 
173
            if percent < 40:
 
174
                logging.warn("Poor network performance detected")
 
175
                return 30
 
176
 
 
177
            logging.debug("Passed benchmark")
 
178
 
 
179
 
 
180
class Interface(socket.socket):
 
181
    """
 
182
    Simple class that provides network interface information.
 
183
    """
 
184
 
 
185
    def __init__(self, interface):
 
186
 
 
187
        super(Interface, self).__init__(
 
188
            socket.AF_INET, socket.IPPROTO_ICMP)
 
189
 
 
190
        self.interface = interface
 
191
 
 
192
        self.dev_path = os.path.join("/sys/class/net", self.interface)
 
193
 
 
194
    def _read_data(self, type):
 
195
        return open(os.path.join(self.dev_path, type)).read().strip()
 
196
 
 
197
    @property
 
198
    def ipaddress(self):
 
199
        freq = struct.pack('256s', self.interface[:15].encode())
 
200
 
 
201
        try:
 
202
            nic_data = fcntl.ioctl(self.fileno(), 0x8915, freq)
 
203
        except IOError:
 
204
            logging.error("No IP address for %s", self.interface)
 
205
            return 1
 
206
        return socket.inet_ntoa(nic_data[20:24])
 
207
 
 
208
    @property
 
209
    def netmask(self):
 
210
        freq = struct.pack('256s', self.interface.encode())
 
211
 
 
212
        try:
 
213
            mask_data = fcntl.ioctl(self.fileno(), 0x891b, freq)
 
214
        except IOError:
 
215
            logging.error("No netmask for %s", self.interface)
 
216
            return 1
 
217
        return socket.inet_ntoa(mask_data[20:24])
 
218
 
 
219
    @property
 
220
    def max_speed(self):
 
221
        return self._read_data("speed")
 
222
 
 
223
    @property
 
224
    def macaddress(self):
 
225
        return self._read_data("address")
 
226
 
 
227
    @property
 
228
    def duplex_mode(self):
 
229
        return self._read_data("duplex")
 
230
 
 
231
    @property
 
232
    def status(self):
 
233
        return self._read_data("operstate")
 
234
 
 
235
    @property
 
236
    def device_name(self):
 
237
        return self._read_data("device/label")
 
238
 
 
239
 
 
240
def interface_test(args):
 
241
    if not "test_type" in vars(args):
 
242
        return
 
243
 
 
244
    # Check and make sure that interface is indeed connected
 
245
    try:
 
246
        cmd = "ip link set dev %s up" % args.interface
 
247
        check_call(shlex.split(cmd))
 
248
    except CalledProcessError as interface_failure:
 
249
        logging.error("Failed to use %s:%s", cmd, interface_failure)
 
250
        return 1
 
251
 
 
252
    # Give interface enough time to get DHCP address
 
253
    time.sleep(10)
 
254
 
 
255
    # Open Network config file
 
256
    DEFAULT_CFG = "/etc/checkbox.d/network.cfg"
 
257
    if not "config" in vars(args):
 
258
        config_file = DEFAULT_CFG
 
259
    else:
 
260
        config_file = args.config
 
261
 
 
262
    config = configparser.SafeConfigParser()
 
263
    config.readfp(open(config_file))
 
264
 
 
265
    # Set default network config options
 
266
    test_target = args.target
 
267
    test_user = args.username
 
268
    test_pass = args.password
 
269
 
 
270
    # Stop all other interfaces
 
271
    extra_interfaces = \
 
272
        [iface for iface in os.listdir("/sys/class/net")
 
273
         if iface != "lo" and iface != args.interface]
 
274
 
 
275
    for iface in extra_interfaces:
 
276
        logging.debug("Shutting down interface:%s", iface)
 
277
        try:
 
278
            cmd = "ip link set dev %s down" % iface
 
279
            check_call(shlex.split(cmd))
 
280
        except CalledProcessError as interface_failure:
 
281
            logging.error("Failed to use %s:%s", cmd, interface_failure)
 
282
            return 1
 
283
 
 
284
    if args.test_type.lower() == "ftp":
 
285
        if test_target is None:
 
286
            # Set FTP parameters based on config file
 
287
            test_target = config.get("FTP", "Target")
 
288
            test_user = config.get("FTP", "User")
 
289
            test_pass = config.get("FTP", "Pass")
 
290
 
 
291
            if test_target == "canonical.com":
 
292
                # Default values found in config file
 
293
                logging.error("Please supply target via: %s", config_file)
 
294
                sys.exit(1)
 
295
 
 
296
        # Being FTP benchmark for specified interface
 
297
        ftp_benchmark = FTPPerformanceTest(
 
298
            args.interface,
 
299
            test_target,
 
300
            test_user,
 
301
            test_pass)
 
302
 
 
303
        if args.filesize:
 
304
            ftp_benchmark.binary_size = int(args.filesize)
 
305
        return ftp_benchmark.run()
 
306
 
 
307
 
 
308
def interface_info(args):
 
309
 
 
310
    info_set = ""
 
311
    if "all" in vars(args):
 
312
        info_set = args.all
 
313
 
 
314
    for key, value in vars(args).items():
 
315
        if value is True or info_set is True:
 
316
            key = key.replace("-", "_")
 
317
            try:
 
318
                print(
 
319
                    key + ":", getattr(Interface(args.interface), key),
 
320
                    file=sys.stderr)
 
321
            except AttributeError:
 
322
                pass
 
323
 
 
324
 
 
325
def main():
 
326
    parser = ArgumentParser(description="Network test module")
 
327
    subparsers = parser.add_subparsers()
 
328
 
 
329
    # Main cli options
 
330
    test_parser = subparsers.add_parser(
 
331
        'test', help=("Run network performance test"))
 
332
    info_parser = subparsers.add_parser(
 
333
        'info', help=("Gather network info"))
 
334
 
 
335
    # Sub test options
 
336
    test_parser.add_argument(
 
337
        '-i', '--interface', type=str, required=True)
 
338
    test_parser.add_argument(
 
339
        '-t', '--test_type', type=str, default="ftp",
 
340
        help=("Choices [FTP *Default*]"))
 
341
    test_parser.add_argument('--target', type=str)
 
342
    test_parser.add_argument('--username', type=str)
 
343
    test_parser.add_argument('--password', type=str)
 
344
    test_parser.add_argument(
 
345
        '--filesize', type=str,
 
346
        help="Size (GB) of binary file to send over network")
 
347
    test_parser.add_argument(
 
348
        '--config', type=str,
 
349
        default="/etc/checkbox.d/network.cfg",
 
350
        help="Supply config file for target/host network parameters")
 
351
 
 
352
    # Sub info options
 
353
    info_parser.add_argument(
 
354
        '-i', '--interface', type=str, required=True)
 
355
    info_parser.add_argument(
 
356
        '--all', default=False, action="store_true")
 
357
    info_parser.add_argument(
 
358
        '--duplex-mode', default=False, action="store_true")
 
359
    info_parser.add_argument(
 
360
        '--max-speed', default=False, action="store_true")
 
361
    info_parser.add_argument(
 
362
        '--ipaddress', default=False, action="store_true")
 
363
    info_parser.add_argument(
 
364
        '--netmask', default=False, action="store_true")
 
365
    info_parser.add_argument(
 
366
        '--device-name', default=False, action="store_true")
 
367
    info_parser.add_argument(
 
368
        '--macaddress', default=False, action="store_true")
 
369
    info_parser.add_argument(
 
370
        '--status', default=False, action="store_true",
 
371
        help=("displays connection status"))
 
372
 
 
373
    test_parser.set_defaults(func=interface_test)
 
374
    info_parser.set_defaults(func=interface_info)
 
375
    
 
376
    args = parser.parse_args()
 
377
 
 
378
    args.func(args)
 
379
 
 
380
 
 
381
if __name__ == "__main__":
 
382
    main()