Commit 44dc5f27 authored by Ignacio Corderi's avatar Ignacio Corderi
Browse files

first commit

parents
Loading
Loading
Loading
Loading

LICENSE/mit.md

0 → 100644
+26 −0
Original line number Diff line number Diff line
The MIT License (MIT)
=====================

Copyright © `2014` `Seagate Technology`

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the “Software”), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

LICENSE/mit.txt

0 → 100644
+21 −0
Original line number Diff line number Diff line
The MIT License (MIT)

Copyright (c) 2014 Seagate Technology

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

README.md

0 → 100644
+35 −0
Original line number Diff line number Diff line
# Kinetic Pyhton Tools
This project contains tools designed to help with deployment and management of Kinetic drives

## Initial Setup
````
git clone https://github.com/Seagate/kinetic-py-tools.git
````

## Available scripts

### Discovery
````
python scripts/discover.py <subnet: 192.168.0>
````
The default output is a file `drives` which contains one line per drive.
Some of the other scripts that operate over a set of drives require this input file.

### Kinetic Drive Firmware Update
````
python scripts/update.py <host> <path>
````

### Kinetic Cluster Update
````
python scripts/cluster_update.py <target_version> <path>
````
This will update all drives in the file `drives` with a version different than `<target_version>`


License
-------

This project is licensed under The MIT License (MIT)
* [Markdown](LICENSE/mit.md) version
* [Original](LICENSE/mit.txt) version
+97 −0
Original line number Diff line number Diff line
# Copyright (c) 2014 Seagate Technology

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

#@author: Ignacio Corderi

import os
import logging
import kinetic
from kinetic.admin import AdminClient

LOG = logging.getLogger(__name__)

def update_cluster(version, binary_path, drives_path):
    drives = []
    with open(drives_path) as f:
        drives = f.readlines()

    toread = os.path.getsize(binary_path)
    data = bytearray(toread)
    try:
        with open(binary_path,'r') as f:
            f.readinto(data)
    except Exception as ex:
        print ex
        LOG.error("Failed to read update binary at {0}.".format(binary_path))
        return

    cs = [AdminClient(x.strip(' \n\t'), chunk_size=1024*1024) for x in drives]

    failed = []
    updated = 0
    skipped = 0
    for c in cs:
        try:
            c.connect()
            config = c.getLog([3]).configuration
            if config.version == version:
                LOG.info("Drive %s is already up to date, skipping it." % c)
                skipped += 1
            else:
                LOG.info("Updating drive %s from version %s..." % (c, config.version))
                c.updateFirmware(data)
                LOG.info("Drive %s updated." % c)
                updated += 1
        except:
            failed.append(c)
            LOG.error("Failed to connect to drive at {0}".format(c))

    LOG.info("Cluster update finished ({0} updated, {1} failed, {2} skipped)."
            .format(updated, len(failed), skipped))

    if len(failed) > 0:
        LOG.info("Printing failed drives.")
        for c in failed: print c

def main():
    import argparse

    parser = argparse.ArgumentParser(description='Kinetic Drive Update Tool')
    parser.add_argument('version', metavar='version',
                       help='Targer version for update.')
    parser.add_argument('path', metavar='path',
                       help='Path to the update binary')
    parser.add_argument('--drives', dest='drives', default="drives",
                       help='Path to file with drives addresses')
    parser.add_argument('--log', dest='loglevel', default="info",
                       help='Logging level (default=warning)')

    args = parser.parse_args()

    numeric_level = getattr(logging, args.loglevel.upper(), None)
    if not isinstance(numeric_level, int):
        raise ValueError('Invalid log level: %s' % loglevel)
    logging.basicConfig(format='%(asctime)-8s %(levelname)s: %(message)s',
                        datefmt="%H:%M:%S", level=numeric_level)

    update_cluster(args.version, args.path, args.drives)

if __name__ == '__main__':
    main()

scripts/discover.py

0 → 100644
+92 −0
Original line number Diff line number Diff line
# Copyright (c) 2014 Seagate Technology

# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:

# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.

# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

#@author: Ignacio Corderi

import logging
from kinetic.admin import AdminClient

LOG = logging.getLogger(__name__)

class DiscoveredDrive(object):

    def __init__(self, config, address):
        self.config = config
        self.availableAt = [address]

def discover(base, timeout, drives_path):
    current = 1

    drives = {}

    if '.' in base and not base.endswith('.'):
        base += '.'

    while current < 255:
        try:
            address = base + str(current)
            c = AdminClient(address, connect_timeout=timeout)
            config = c.getLog([3]).configuration

            LOG.info("Discovered a drive at {0} (SN={1})"
                     .format(address, config.serialNumber))

            if config.serialNumber in drives:
                drives[config.serialNumber].availableAt.append(address)
            else:
                drives[config.serialNumber] = DiscoveredDrive(config, address)

        except: pass

        current += 1

    with open(drives_path,'w+') as f:
        for sn, d in drives.iteritems():
            LOG.info("Drive with SN={0} (Version={2}) available at {1}"
                     .format(sn, d.availableAt, d.config.version))
            f.write(d.availableAt[0] + "\n")

    LOG.info("Discovered {0} drives.".format(len(drives)))

def main():
    import argparse

    parser = argparse.ArgumentParser(description='Kinetic Discovery Tool')
    parser.add_argument('subnet', metavar='subnet',
                       help='Subnet to scan (i.e. 192.168.33.)')
    parser.add_argument('--timeout', dest='timeout', type=int, default=50,
                       help='Connect timeout in ms (default=50)')
    parser.add_argument('--output', dest='drives', default="drives",
                       help='Path to file with drives addresses')
    parser.add_argument('--log', dest='loglevel', default="info",
                       help='Logging level (default=info)')

    args = parser.parse_args()

    numeric_level = getattr(logging, args.loglevel.upper(), None)
    if not isinstance(numeric_level, int):
        raise ValueError('Invalid log level: %s' % loglevel)
    logging.basicConfig(format='%(asctime)-8s %(levelname)s: %(message)s',
                        datefmt="%H:%M:%S", level=numeric_level)

    discover(args.subnet, timeout=(args.timeout / 1000.), drives_path=args.drives)

if __name__ == '__main__':
    main()