[lnst] Added lnst-pool-wizard
by Jiří Pírko
commit 4c18914f6cb735d206fff8fbfc77ce5aefe8677f
Author: Jiri Prochazka <jprochaz(a)redhat.com>
Date: Tue Sep 23 13:39:45 2014 +0200
Added lnst-pool-wizard
More info on: https://github.com/jpirko/lnst/wiki/Pool-Wizard
Signed-off-by: Jiri Prochazka <jprochaz(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst-pool-wizard | 66 ++++++++++
lnst/Controller/MessageDispatcherLite.py | 65 ++++++++++
lnst/Controller/Wizard.py | 205 ++++++++++++++++++++++++++++++
lnst/Slave/InterfaceManager.py | 2 +
lnst/Slave/NetTestSlave.py | 9 ++
5 files changed, 347 insertions(+), 0 deletions(-)
---
diff --git a/lnst-pool-wizard b/lnst-pool-wizard
new file mode 100755
index 0000000..57ea491
--- /dev/null
+++ b/lnst-pool-wizard
@@ -0,0 +1,66 @@
+#! /usr/bin/env python
+"""
+Machine pool wizard
+
+Copyright 2014 Red Hat, Inc.
+Licensed under the GNU General Public License, version 2 as
+published by the Free Software Foundation; see COPYING for details.
+"""
+
+__author__ = """
+jprochaz(a)redhat.com (Jiri Prochazka)
+"""
+from lnst.Controller.Wizard import Wizard
+import sys
+import getopt
+
+RETVAL_PASS = 0
+RETVAL_ERR = 1
+
+def help(retval = 0):
+ print "Usage:\n"\
+ " lnst-pool-wizard [mode] [hostname[:port]]\n"\
+ "\n"\
+ "Modes:\n"\
+ " -h, --help display this help text and exit\n"\
+ " -i, --interactive start wizard in interactive mode (this "\
+ "is default mode)\n"\
+ " -n, --noninteractive start wizard in noninteractive mode\n"\
+ "Examples:\n"\
+ " lnst-pool-wizard --interactive\n"\
+ " lnst-pool-wizard --noninteractive 192.168.122.2\n"\
+ " lnst-pool-wizard -n 192.168.122.2:8888 192.168.122.3:9999 192.168.122.4\n"
+ sys.exit(retval)
+
+
+def main():
+ try:
+ opts, args = getopt.getopt(
+ sys.argv[1:],
+ "hin",
+ ["help", "interactive", "noninteractive"]
+ )
+ except getopt.GetoptError as err:
+ print str(err)
+ help(RETVAL_ERR)
+ wizard = Wizard()
+ for opt, arg in opts:
+ if opt in ("-h", "--help"):
+ help()
+ elif opt in ("-i", "--interactive"):
+ wizard.interactive()
+ sys.exit(RETVAL_PASS)
+ elif opt in ("-n", "--noninteractive"):
+ if not args:
+ print "No hostname entered!"
+ return RETVAL_ERR
+ wizard.noninteractive(args)
+ sys.exit(RETVAL_PASS)
+ else:
+ help(RET_ERR)
+ wizard.interactive()
+ sys.exit(RETVAL_PASS)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lnst/Controller/MessageDispatcherLite.py b/lnst/Controller/MessageDispatcherLite.py
new file mode 100644
index 0000000..70a006a
--- /dev/null
+++ b/lnst/Controller/MessageDispatcherLite.py
@@ -0,0 +1,65 @@
+"""
+This module defines MessageDispatcherLite class which is derived from
+lnst.NetTestController.MessageDispatcher.
+
+Copyright 2011 Red Hat, Inc.
+Licensed under the GNU General Public License, version 2 as
+published by the Free Software Foundation; see COPYING for details.
+"""
+
+__author__ = """
+jpirko(a)redhat.com (Jiri Pirko)
+"""
+
+from lnst.Common.ConnectionHandler import send_data, recv_data
+from lnst.Common.ConnectionHandler import ConnectionHandler
+
+class MessageDispatcherLite(ConnectionHandler):
+ def __init__(self):
+ super(MessageDispatcherLite, self).__init__()
+
+ def add_slave(self, machine, connection):
+ self.add_connection(1, connection)
+
+ def send_message(self, machine_id, data):
+ soc = self.get_connection(1)
+
+ if send_data(soc, data) == False:
+ msg = "Connection error from slave %s" % str(1)
+ raise NetTestError(msg)
+
+ def wait_for_result(self, machine_id):
+ wait = True
+ while wait:
+ connected_slaves = self._connections.keys()
+
+ messages = self.check_connections()
+
+ remaining_slaves = self._connections.keys()
+
+ for msg in messages:
+ if msg[1]["type"] == "result" and msg[0] == 1:
+ wait = False
+ result = msg[1]["result"]
+ else:
+ self._process_message(msg)
+
+ if connected_slaves != remaining_slaves:
+ disconnected_slaves = set(connected_slaves) -\
+ set(remaining_slaves)
+ msg = "Slaves " + str(list(disconnected_slaves)) + \
+ " disconnected from the controller."
+ raise NetTestError(msg)
+
+ return result
+
+ def _process_message(self, message):
+ if message[1]["type"] == "log":
+ pass
+ else:
+ msg = "Unknown message type: %s" % message[1]["type"]
+ raise NetTestError(msg)
+
+ def disconnect_slave(self, machine_id):
+ soc = self.get_connection(machine_id)
+ self.remove_connection(soc)
diff --git a/lnst/Controller/Wizard.py b/lnst/Controller/Wizard.py
new file mode 100644
index 0000000..f16c8fa
--- /dev/null
+++ b/lnst/Controller/Wizard.py
@@ -0,0 +1,205 @@
+"""
+Wizard class for creating machine pool .xml files
+
+Copyright 2014 Red Hat Inc.
+Licensed under the GNU General Public Licence, version 2 as
+published by the Free Software Foundation; see COPYING for details.
+"""
+
+__author__ = """
+jprochaz(a)redhat.com (Jiri Prochazka)
+"""
+import socket
+import sys
+import os
+from lnst.Controller.Machine import Machine
+from lnst.Controller.MessageDispatcherLite import MessageDispatcherLite
+from lnst.Common.NetUtils import test_tcp_connection
+from lnst.Common.Utils import mkdir_p
+from lnst.Common.Config import DefaultRPCPort
+from xml.dom.minidom import getDOMImplementation
+
+class Wizard:
+ def __init__(self):
+ self._msg_dispatcher = MessageDispatcherLite()
+ self._pool_dir = os.path.expanduser("~/.lnst/pool")
+
+ def interactive(self):
+ """
+ Starts Wizard in interactive mode. Wizard requests hostname and port
+ from user, tests connectivity to entered host, then he tries to connect
+ and configure slave on host machine, and finally he requests list of
+ ethernet interfaces in state DOWN. Then he writes them into .xml file
+ named by user. User can choose which interfaces should be added to the
+ .xml file.
+ """
+ while True:
+ pool_dir = raw_input("Enter path to pool directory "\
+ "(default: ~/.lnst/pool): ")
+ if pool_dir != "":
+ self._pool_dir = os.path.expanduser(pool_dir)
+ print "Pool directory set to %s" % self._pool_dir
+ while True:
+ hostname = raw_input("Enter hostname: ")
+ try:
+ # Tests if hostname is translatable into IP address
+ socket.gethostbyname(hostname)
+ break
+ except:
+ sys.stderr.write("Hostname is not translatable into valid"\
+ " IP address.\n")
+ continue
+ while True:
+ port = raw_input("Enter port(default: 9999): ")
+ if port == "":
+ port = DefaultRPCPort
+ try:
+ port = int(port)
+ break
+ except:
+ sys.stderr.write("Invalid port.")
+ continue
+ msg = self._get_suitable_interfaces(socket.gethostbyname(hostname),\
+ port)
+ if msg:
+ self._write_to_xml(msg, hostname, port, "interactive")
+ next_machine = raw_input("Do you want to add another machine? "\
+ "[Y/n]: ")
+ if next_machine.lower() == 'y' or next_machine == "":
+ continue
+ else:
+ break
+ return
+
+ def noninteractive(self, hostlist):
+ """
+ Starts Wizard in noninteractive mode. Wizard gets list of hosts and
+ ports as arguments. He tries to connect and get info about suitable
+ interfaces for each of the hosts. Noninteractive mode does not prompt
+ user about anything, it automatically adds all suitable interfaces into
+ .xml file named the same as the hostname of the selected machine.
+ """
+ self._mode = "noninteractive"
+
+ for host in hostlist:
+ # Checks if port was entered along with hostname
+ if host.find(":") != -1:
+ hostname = host.split(':')[0]
+ try:
+ port = int(host.split(':')[1])
+ except:
+ port = DefaultRPCPort
+ else:
+ hostname = host
+ port = DefaultRPCPort
+ msg = self._get_suitable_interfaces(hostname, port)
+ if not msg:
+ continue
+ self._write_to_xml(msg, hostname, port, "noninteractive")
+
+ def _get_suitable_interfaces(self, hostname, port):
+ """
+ Calls all functions, which are used by both interactive and
+ noninteractive mode to get list of suitable interfaces. The list is
+ saved to variable msg.
+ """
+ if not test_tcp_connection(hostname, port):
+ sys.stderr.write("Host destination '%s:%s' unreachable.\n"
+ % (hostname, port))
+ return False
+ if not self._connect_and_configure_machine(hostname, port):
+ return False
+ msg = self._get_device_ifcs(hostname, port)
+ self._msg_dispatcher.disconnect_slave(1)
+ return msg
+
+ def _get_device_ifcs(self, hostname, port):
+ """
+ Sends RPC call request to Slave to call function get_devices, which
+ returns list of ethernet devices which are in state DOWN.
+ """
+ msg = self._machine._rpc_call("get_devices")
+ if msg == {}:
+ sys.stderr.write("No suitable interfaces found on the slave "\
+ "'%s:%s'.\n" % (hostname, port))
+ return False
+ return msg
+
+ def _connect_and_configure_machine(self, hostname, port):
+ """
+ Connects to Slave and configures it
+ """
+ try:
+ self._machine = Machine(1, hostname, None, port)
+ self._machine.set_rpc(self._msg_dispatcher)
+ self._machine.configure("MachinePoolWizard")
+ return True
+ except:
+ sys.stderr.write("Remote machine '%s:%s' configuration failed!\n"
+ % (hostname, port))
+ self._msg_dispatcher.disconnect_slave(1)
+ return False
+
+ def _write_to_xml(self, msg, hostname, port, mode):
+ """
+ Used for writing desired output into .xml file. In interactive mode,
+ user is prompted for every interface, in noninteractive mode all
+ interfaces are automatically added to the .xml file.
+ """
+ if mode == "interactive":
+ output_file = raw_input("Enter the name of the output .xml file "\
+ "(without .xml, default is hostname.xml): ")
+ if mode == "noninteractive" or output_file == "":
+ output_file = hostname
+
+ impl = getDOMImplementation()
+ doc = impl.createDocument(None, "slavemachine", None)
+ top_el = doc.documentElement
+ params_el = doc.createElement("params")
+ top_el.appendChild(params_el)
+ param_el = doc.createElement("param")
+ param_el.setAttribute("name", "hostname")
+ param_el.setAttribute("value", hostname)
+ params_el.appendChild(param_el)
+ interfaces_el = doc.createElement("interfaces")
+ top_el.appendChild(interfaces_el)
+
+ devices_added = 0
+ for interface in msg.itervalues():
+ if mode == 'interactive':
+ answer = raw_input("Do you want to add interface '%s' (%s) "
+ "to the recipe? [Y/n]" % (interface['name'],
+ interface['hwaddr']))
+ if mode == "noninteractive" or answer.lower() == 'y'\
+ or answer == "":
+ devices_added += 1
+ eth_el = doc.createElement("eth")
+ eth_el.setAttribute("id", interface['name'])
+ eth_el.setAttribute("label", "default_network")
+ interfaces_el.appendChild(eth_el)
+ params_el = doc.createElement("params")
+ eth_el.appendChild(params_el)
+ param_el = doc.createElement("param")
+ param_el.setAttribute("name", "hwaddr")
+ param_el.setAttribute("value", interface['hwaddr'])
+ params_el.appendChild(param_el)
+ if devices_added == 0:
+ sys.stderr.write("You didn't add any interface, no file '%s.xml' "\
+ "will be created!\n" % output_file)
+ return
+
+ mkdir_p(self._pool_dir)
+
+ try:
+ f = open(self._pool_dir + "/" + output_file + ".xml", 'w')
+ f.write(doc.toprettyxml())
+ f.close()
+ except:
+ sys.stderr.write("File '%s.xml' could not be opened "\
+ "or data written." % output_file+"\n")
+ raise WizardException()
+
+ print "File '%s.xml' successfuly created." % output_file
+
+class WizardException(Exception):
+ pass
diff --git a/lnst/Slave/InterfaceManager.py b/lnst/Slave/InterfaceManager.py
index f83dd33..5353eb8 100644
--- a/lnst/Slave/InterfaceManager.py
+++ b/lnst/Slave/InterfaceManager.py
@@ -240,6 +240,7 @@ class Device(object):
self._conf = None
self._conf_dict = None
self._ip = None
+ self._ifi_type = None
self._state = None
self._master = {"primary": None, "other": []}
self._slaves = []
@@ -249,6 +250,7 @@ class Device(object):
def init_netlink(self, nl_msg):
self._if_index = nl_msg['index']
+ self._ifi_type = nl_msg['ifi_type']
self._hwaddr = normalize_hwaddr(nl_msg.get_attr("IFLA_ADDRESS"))
self._name = nl_msg.get_attr("IFLA_IFNAME")
self._state = nl_msg.get_attr("IFLA_OPERSTATE")
diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py
index 74ffba0..ff29e9e 100644
--- a/lnst/Slave/NetTestSlave.py
+++ b/lnst/Slave/NetTestSlave.py
@@ -119,6 +119,15 @@ class SlaveMethods:
return devices
+ def get_devices(self):
+ self._if_manager.rescan_devices()
+ devices = self._if_manager.get_devices()
+ result = {}
+ for device in devices:
+ if device._ifi_type == 1 and device._state == 'DOWN':
+ result[device._if_index] = {'name' : device._name, 'hwaddr' : device._hwaddr}
+ return result
+
def get_devices_by_devname(self, devname):
name_scan = self._if_manager.get_devices()
netdevs = []
9 years, 6 months
[lnst] Controller: add python task path to syspath
by Jiří Pírko
commit 29c00b9c4dcd31337bd1c8d1e1bf62f9a3271684
Author: Jan Tluka <jtluka(a)redhat.com>
Date: Mon Sep 22 17:01:09 2014 +0200
Controller: add python task path to syspath
If a task python is ran and it has some dependency on a module located
in the same directory as the task then the run will fail because the
PYTHONPATH does not contain the task directory.
The patch appends the task directory to the system path variable before
loading python task code and removes it from it afterwards.
An example:
$ ls ~/my_recipes
task.py
ciphers.py
$ cat ~/my_recipes/task.py
from lnst.Controller.Task import ctl
import ciphers
...
$ lnst-ctl run ~/my_recipes/task.py
...
File "/root/lnst/lnst/Controller/NetTestController.py", line 664, in _run_python_task
module = imp.load_source(name, task_path.resolve())
File "/root/testplans/my_recipes/task.py", line 21, in <module>
import ciphers
ImportError: No module named ciphers
Signed-off-by: Jan Tluka <jtluka(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst/Controller/NetTestController.py | 3 +++
1 files changed, 3 insertions(+), 0 deletions(-)
---
diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py
index 04c67da..2830682 100644
--- a/lnst/Controller/NetTestController.py
+++ b/lnst/Controller/NetTestController.py
@@ -19,6 +19,7 @@ import cPickle
import tempfile
import imp
import copy
+import sys
from time import sleep
from xmlrpclib import Binary
from lnst.Common.NetUtils import MacPool
@@ -661,7 +662,9 @@ class NetTestController:
task_path = task["python"]
name = os.path.basename(task_path.abs_path()).split(".")[0]
+ sys.path.append(os.path.dirname(task_path.resolve()))
module = imp.load_source(name, task_path.resolve())
+ sys.path.remove(os.path.dirname(task_path.resolve()))
#restore resource table
self._resource_table = res_table_bkp
9 years, 6 months
[lnst] lnst-ctl: move recipe_head_log_entry to correct place
by Jiří Pírko
commit 379c8edb87b801b45d5600a08bd8802f3f6ede62
Author: Jan Tluka <jtluka(a)redhat.com>
Date: Mon Sep 22 15:28:26 2014 +0200
lnst-ctl: move recipe_head_log_entry to correct place
The function recipe_head_log_entry was at the incorrect place in the
code that resulted in having the first log entry "Processing recipe
file ..." for a recipe in the root debug file (for the first recipe)
or in the previous recipe (for all subsequent recipes).
Signed-off-by: Jan Tluka <jtluka(a)redhat.com>
Signed-off-by: Jiri Pirko <jiri(a)resnulli.us>
lnst-ctl | 3 +--
1 files changed, 1 insertions(+), 2 deletions(-)
---
diff --git a/lnst-ctl b/lnst-ctl
index 008d4ea..6ff00ab 100755
--- a/lnst-ctl
+++ b/lnst-ctl
@@ -90,6 +90,7 @@ def get_recipe_result(args, file_path, res_serializer, packet_capture, log_ctl,
reduce_sync):
res_serializer.add_recipe(file_path)
log_ctl.set_recipe(file_path)
+ recipe_head_log_entry(file_path)
retval = RETVAL_PASS
@@ -242,7 +243,6 @@ def main():
for f in all_files:
recipe_file = os.path.join(recipe_path, f)
if re.match(r'^.*\.xml$', recipe_file):
- recipe_head_log_entry(recipe_file)
fp, res, rv = get_recipe_result(action, recipe_file,
res_serializer,
packet_capture,
@@ -251,7 +251,6 @@ def main():
overriden_aliases,
reduce_sync)
else:
- recipe_head_log_entry(recipe_path)
fp, res, rv = get_recipe_result(action, recipe_path,
res_serializer,
packet_capture,
9 years, 6 months
[PATCH] Controller: add python task path to syspath
by Jan Tluka
If a task python is ran and it has some dependency on a module located
in the same directory as the task then the run will fail because the
PYTHONPATH does not contain the task directory.
The patch appends the task directory to the system path variable before
loading python task code and removes it from it afterwards.
An example:
$ ls ~/my_recipes
task.py
ciphers.py
$ cat ~/my_recipes/task.py
from lnst.Controller.Task import ctl
import ciphers
...
$ lnst-ctl run ~/my_recipes/task.py
...
File "/root/lnst/lnst/Controller/NetTestController.py", line 664, in _run_python_task
module = imp.load_source(name, task_path.resolve())
File "/root/testplans/my_recipes/task.py", line 21, in <module>
import ciphers
ImportError: No module named ciphers
Signed-off-by: Jan Tluka <jtluka(a)redhat.com>
---
lnst/Controller/NetTestController.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lnst/Controller/NetTestController.py b/lnst/Controller/NetTestController.py
index 04c67da..2830682 100644
--- a/lnst/Controller/NetTestController.py
+++ b/lnst/Controller/NetTestController.py
@@ -19,6 +19,7 @@ import cPickle
import tempfile
import imp
import copy
+import sys
from time import sleep
from xmlrpclib import Binary
from lnst.Common.NetUtils import MacPool
@@ -661,7 +662,9 @@ class NetTestController:
task_path = task["python"]
name = os.path.basename(task_path.abs_path()).split(".")[0]
+ sys.path.append(os.path.dirname(task_path.resolve()))
module = imp.load_source(name, task_path.resolve())
+ sys.path.remove(os.path.dirname(task_path.resolve()))
#restore resource table
self._resource_table = res_table_bkp
--
1.9.3
9 years, 6 months
[PATCH] lnst-ctl: move recipe_head_log_entry to correct place
by Jan Tluka
The function recipe_head_log_entry was at the incorrect place in the
code that resulted in having the first log entry "Processing recipe
file ..." for a recipe in the root debug file (for the first recipe)
or in the previous recipe (for all subsequent recipes).
Signed-off-by: Jan Tluka <jtluka(a)redhat.com>
---
lnst-ctl | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/lnst-ctl b/lnst-ctl
index 008d4ea..6ff00ab 100755
--- a/lnst-ctl
+++ b/lnst-ctl
@@ -90,6 +90,7 @@ def get_recipe_result(args, file_path, res_serializer, packet_capture, log_ctl,
reduce_sync):
res_serializer.add_recipe(file_path)
log_ctl.set_recipe(file_path)
+ recipe_head_log_entry(file_path)
retval = RETVAL_PASS
@@ -242,7 +243,6 @@ def main():
for f in all_files:
recipe_file = os.path.join(recipe_path, f)
if re.match(r'^.*\.xml$', recipe_file):
- recipe_head_log_entry(recipe_file)
fp, res, rv = get_recipe_result(action, recipe_file,
res_serializer,
packet_capture,
@@ -251,7 +251,6 @@ def main():
overriden_aliases,
reduce_sync)
else:
- recipe_head_log_entry(recipe_path)
fp, res, rv = get_recipe_result(action, recipe_path,
res_serializer,
packet_capture,
--
1.9.3
9 years, 6 months
[PATCH] Added lnst-pool-wizard v2.
by Jiri Prochazka
More info on: https://github.com/jpirko/lnst/wiki/Pool-Wizard
Signed-off-by: Jiri Prochazka <jprochaz(a)redhat.com>
---
lnst-pool-wizard | 66 +++++++++++
lnst/Controller/MessageDispatcherLite.py | 65 ++++++++++
lnst/Controller/Wizard.py | 198 +++++++++++++++++++++++++++++++
lnst/Slave/InterfaceManager.py | 2 +
lnst/Slave/NetTestSlave.py | 9 ++
5 files changed, 340 insertions(+)
create mode 100755 lnst-pool-wizard
create mode 100644 lnst/Controller/MessageDispatcherLite.py
create mode 100644 lnst/Controller/Wizard.py
diff --git a/lnst-pool-wizard b/lnst-pool-wizard
new file mode 100755
index 0000000..57ea491
--- /dev/null
+++ b/lnst-pool-wizard
@@ -0,0 +1,66 @@
+#! /usr/bin/env python
+"""
+Machine pool wizard
+
+Copyright 2014 Red Hat, Inc.
+Licensed under the GNU General Public License, version 2 as
+published by the Free Software Foundation; see COPYING for details.
+"""
+
+__author__ = """
+jprochaz(a)redhat.com (Jiri Prochazka)
+"""
+from lnst.Controller.Wizard import Wizard
+import sys
+import getopt
+
+RETVAL_PASS = 0
+RETVAL_ERR = 1
+
+def help(retval = 0):
+ print "Usage:\n"\
+ " lnst-pool-wizard [mode] [hostname[:port]]\n"\
+ "\n"\
+ "Modes:\n"\
+ " -h, --help display this help text and exit\n"\
+ " -i, --interactive start wizard in interactive mode (this "\
+ "is default mode)\n"\
+ " -n, --noninteractive start wizard in noninteractive mode\n"\
+ "Examples:\n"\
+ " lnst-pool-wizard --interactive\n"\
+ " lnst-pool-wizard --noninteractive 192.168.122.2\n"\
+ " lnst-pool-wizard -n 192.168.122.2:8888 192.168.122.3:9999 192.168.122.4\n"
+ sys.exit(retval)
+
+
+def main():
+ try:
+ opts, args = getopt.getopt(
+ sys.argv[1:],
+ "hin",
+ ["help", "interactive", "noninteractive"]
+ )
+ except getopt.GetoptError as err:
+ print str(err)
+ help(RETVAL_ERR)
+ wizard = Wizard()
+ for opt, arg in opts:
+ if opt in ("-h", "--help"):
+ help()
+ elif opt in ("-i", "--interactive"):
+ wizard.interactive()
+ sys.exit(RETVAL_PASS)
+ elif opt in ("-n", "--noninteractive"):
+ if not args:
+ print "No hostname entered!"
+ return RETVAL_ERR
+ wizard.noninteractive(args)
+ sys.exit(RETVAL_PASS)
+ else:
+ help(RET_ERR)
+ wizard.interactive()
+ sys.exit(RETVAL_PASS)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lnst/Controller/MessageDispatcherLite.py b/lnst/Controller/MessageDispatcherLite.py
new file mode 100644
index 0000000..70a006a
--- /dev/null
+++ b/lnst/Controller/MessageDispatcherLite.py
@@ -0,0 +1,65 @@
+"""
+This module defines MessageDispatcherLite class which is derived from
+lnst.NetTestController.MessageDispatcher.
+
+Copyright 2011 Red Hat, Inc.
+Licensed under the GNU General Public License, version 2 as
+published by the Free Software Foundation; see COPYING for details.
+"""
+
+__author__ = """
+jpirko(a)redhat.com (Jiri Pirko)
+"""
+
+from lnst.Common.ConnectionHandler import send_data, recv_data
+from lnst.Common.ConnectionHandler import ConnectionHandler
+
+class MessageDispatcherLite(ConnectionHandler):
+ def __init__(self):
+ super(MessageDispatcherLite, self).__init__()
+
+ def add_slave(self, machine, connection):
+ self.add_connection(1, connection)
+
+ def send_message(self, machine_id, data):
+ soc = self.get_connection(1)
+
+ if send_data(soc, data) == False:
+ msg = "Connection error from slave %s" % str(1)
+ raise NetTestError(msg)
+
+ def wait_for_result(self, machine_id):
+ wait = True
+ while wait:
+ connected_slaves = self._connections.keys()
+
+ messages = self.check_connections()
+
+ remaining_slaves = self._connections.keys()
+
+ for msg in messages:
+ if msg[1]["type"] == "result" and msg[0] == 1:
+ wait = False
+ result = msg[1]["result"]
+ else:
+ self._process_message(msg)
+
+ if connected_slaves != remaining_slaves:
+ disconnected_slaves = set(connected_slaves) -\
+ set(remaining_slaves)
+ msg = "Slaves " + str(list(disconnected_slaves)) + \
+ " disconnected from the controller."
+ raise NetTestError(msg)
+
+ return result
+
+ def _process_message(self, message):
+ if message[1]["type"] == "log":
+ pass
+ else:
+ msg = "Unknown message type: %s" % message[1]["type"]
+ raise NetTestError(msg)
+
+ def disconnect_slave(self, machine_id):
+ soc = self.get_connection(machine_id)
+ self.remove_connection(soc)
diff --git a/lnst/Controller/Wizard.py b/lnst/Controller/Wizard.py
new file mode 100644
index 0000000..9255796
--- /dev/null
+++ b/lnst/Controller/Wizard.py
@@ -0,0 +1,198 @@
+"""
+Wizard class for creating machine pool .xml files
+
+Copyright 2014 Red Hat Inc.
+Licensed under the GNU General Public Licence, version 2 as
+published by the Free Software Foundation; see COPYING for details.
+"""
+
+__author__ = """
+jprochaz(a)redhat.com (Jiri Prochazka)
+"""
+import socket
+import sys
+import os
+from lnst.Controller.Machine import Machine
+from lnst.Controller.MessageDispatcherLite import MessageDispatcherLite
+from lnst.Common.NetUtils import test_tcp_connection
+from lnst.Common.Utils import mkdir_p
+from lnst.Common.Config import DefaultRPCPort
+
+class Wizard:
+ def __init__(self):
+ self._msg_dispatcher = MessageDispatcherLite()
+ self._pool_dir = os.path.expanduser("~/.lnst/pool")
+
+ def interactive(self):
+ """
+ Starts Wizard in interactive mode. Wizard requests hostname and port
+ from user, tests connectivity to entered host, then he tries to connect
+ and configure slave on host machine, and finally he requests list of
+ ethernet interfaces in state DOWN. Then he writes them into .xml file
+ named by user. User can choose which interfaces should be added to the
+ .xml file.
+ """
+ while True:
+ pool_dir = raw_input("Enter path to pool directory "\
+ "(default: ~/.lnst/pool): ")
+ if pool_dir != "":
+ self._pool_dir = os.path.expanduser(pool_dir)
+ print "Pool directory set to %s" % self._pool_dir
+ while True:
+ hostname = raw_input("Enter hostname: ")
+ try:
+ # Tests if hostname is translatable into IP address
+ socket.gethostbyname(hostname)
+ break
+ except:
+ sys.stderr.write("Hostname is not translatable into valid"\
+ " IP address.\n")
+ continue
+ while True:
+ port = raw_input("Enter port(default: 9999): ")
+ if port == "":
+ port = DefaultRPCPort
+ try:
+ port = int(port)
+ break
+ except:
+ sys.stderr.write("Invalid port.")
+ continue
+ msg = self._get_suitable_interfaces(socket.gethostbyname(hostname),\
+ port)
+ if msg:
+ self._write_to_xml(msg, hostname, port, "interactive")
+ next_machine = raw_input("Do you want to add another machine? "\
+ "[Y/n]: ")
+ if next_machine.lower() == 'y' or next_machine == "":
+ continue
+ else:
+ break
+ return
+
+ def noninteractive(self, hostlist):
+ """
+ Starts Wizard in noninteractive mode. Wizard gets list of hosts and
+ ports as arguments. He tries to connect and get info about suitable
+ interfaces for each of the hosts. Noninteractive mode does not prompt
+ user about anything, it automatically adds all suitable interfaces into
+ .xml file named the same as the hostname of the selected machine.
+ """
+ self._mode = "noninteractive"
+
+ for host in hostlist:
+ # Checks if port was entered along with hostname
+ if host.find(":") != -1:
+ hostname = host.split(':')[0]
+ try:
+ port = int(host.split(':')[1])
+ except:
+ port = DefaultRPCPort
+ else:
+ hostname = host
+ port = DefaultRPCPort
+ msg = self._get_suitable_interfaces(hostname, port)
+ if not msg:
+ continue
+ self._write_to_xml(msg, hostname, port, "noninteractive")
+
+ def _get_suitable_interfaces(self, hostname, port):
+ """
+ Calls all functions, which are used by both interactive and
+ noninteractive mode to get list of suitable interfaces. The list is
+ saved to variable msg.
+ """
+ if not test_tcp_connection(hostname, port):
+ sys.stderr.write("Host destination '%s:%s' unreachable.\n"
+ % (hostname, port))
+ return False
+ if not self._connect_and_configure_machine(hostname, port):
+ return False
+ msg = self._get_device_ifcs(hostname, port)
+ self._msg_dispatcher.disconnect_slave(1)
+ return msg
+
+ def _get_device_ifcs(self, hostname, port):
+ """
+ Sends RPC call request to Slave to call function get_devices, which
+ returns list of ethernet devices which are in state DOWN.
+ """
+ msg = self._machine._rpc_call("get_devices")
+ if msg == {}:
+ sys.stderr.write("No suitable interfaces found on the slave "\
+ "'%s:%s'.\n" % (hostname, port))
+ return False
+ return msg
+
+ def _connect_and_configure_machine(self, hostname, port):
+ """
+ Connects to Slave and configures it
+ """
+ try:
+ self._machine = Machine(1, hostname, None, port)
+ self._machine.set_rpc(self._msg_dispatcher)
+ self._machine.configure("MachinePoolWizard")
+ return True
+ except:
+ sys.stderr.write("Remote machine '%s:%s' configuration failed!\n"
+ % (hostname, port))
+ self._msg_dispatcher.disconnect_slave(1)
+ return False
+
+ def _write_to_xml(self, msg, hostname, port, mode):
+ """
+ Used for writing desired output into .xml file. In interactive mode,
+ user is prompted for every interface, in noninteractive mode all
+ interfaces are automatically added to the .xml file.
+ """
+ if mode == "interactive":
+ output_file = raw_input("Enter the name of the output .xml file "\
+ "(without .xml, default is hostname.xml): ")
+ if mode == "noninteractive" or output_file == "":
+ output_file = hostname
+ xml_file = "<slavemachine>\n"\
+ " <params>\n"\
+ " <param name=\"hostname\" value=\"%s\"/>\n"\
+ " </params>\n"\
+ " <interfaces>\n" % hostname
+ devices_added = 0
+ for interface in msg.itervalues():
+ if mode == 'interactive':
+ answer = raw_input("Do you want to add interface '%s' (%s) "
+ "to the recipe? [Y/n]" % (interface['name'],
+ interface['hwaddr']))
+ if mode == "noninteractive" or answer.lower() == 'y'\
+ or answer == "":
+ devices_added += 1
+ xml_file += " <eth label=\"default_network\" "\
+ "id=\"%s\">\n"\
+ " <params>\n"\
+ " <param name=\"hwaddr\" "\
+ "value=\"%s\"/>\n"\
+ " </params>\n"\
+ " </eth>\n" % (interface['name'],
+ interface['hwaddr'])
+ xml_file += " </interfaces>\n"\
+ "</slavemachine>\n"
+ if devices_added == 0:
+ sys.stderr.write("You didn't add any interface, no file '%s.xml' "\
+ "will be created!" % output_file)
+ return
+ try:
+ mkdir_p(self._pool_dir)
+ except:
+ raise WizardException()
+
+ try:
+ f = open(self._pool_dir + "/" + output_file + ".xml", 'w')
+ f.write(xml_file)
+ f.close()
+ except:
+ sys.stderr.write("File '%s.xml' could not be opened "\
+ "or data written." % output_file+"\n")
+ raise WizardException()
+
+ print "File '%s.xml' successfuly created." % output_file
+
+class WizardException(Exception):
+ pass
diff --git a/lnst/Slave/InterfaceManager.py b/lnst/Slave/InterfaceManager.py
index e30b52a..fc29af0 100644
--- a/lnst/Slave/InterfaceManager.py
+++ b/lnst/Slave/InterfaceManager.py
@@ -238,6 +238,7 @@ class Device(object):
self._conf = None
self._conf_dict = None
self._ip = None
+ self._ifi_type = None
self._state = None
self._master = {"primary": None, "other": []}
self._slaves = []
@@ -247,6 +248,7 @@ class Device(object):
def init_netlink(self, nl_msg):
self._if_index = nl_msg['index']
+ self._ifi_type = nl_msg['ifi_type']
self._hwaddr = normalize_hwaddr(nl_msg.get_attr("IFLA_ADDRESS"))
self._name = nl_msg.get_attr("IFLA_IFNAME")
self._state = nl_msg.get_attr("IFLA_OPERSTATE")
diff --git a/lnst/Slave/NetTestSlave.py b/lnst/Slave/NetTestSlave.py
index 0fbbcff..ec6dddb 100644
--- a/lnst/Slave/NetTestSlave.py
+++ b/lnst/Slave/NetTestSlave.py
@@ -119,6 +119,15 @@ class SlaveMethods:
return devices
+ def get_devices(self):
+ self._if_manager.rescan_devices()
+ devices = self._if_manager.get_devices()
+ result = {}
+ for device in devices:
+ if device._ifi_type == 1 and device._state == 'DOWN':
+ result[device._if_index] = {'name' : device._name, 'hwaddr' : device._hwaddr}
+ return result
+
def get_devices_by_devname(self, devname):
name_scan = self._if_manager.get_devices()
netdevs = []
--
1.9.3
9 years, 6 months
“微”机也就来了,你知道这意味着什么!
by 广州市悦鑫经贸
659992广州市悦鑫经贸
10年前,互联网来了,有人因此成为商业巨头;
5年前,淘宝来了,有人因此实现“草根创业”;
3年前,微博来了,有人因此实现财富“核裂变”;
而今天,微信来了,微营销来了……
7天连锁酒店通过微信营销,一个月内,会员从30万几何式增至120万!
小米手机通过微信营销,在短短3个月内吸引粉丝105万,网上订单暴增15倍!
星巴克通过微信营销,在三周内,仅“冰摇沁爽”一项产品销售额就突破750万!
“90后”大学生通过微信营销卖水果,一没店铺,二没员工情况下,实现月入8万的奇迹!
微信来了,“微”机也就来了,你知道这意味着什么!!
未来十年,是中国商业领域大规模打劫的时代,所有还在采用传统运营模式的企业的“粮仓”都有可能
遭遇打劫,而那些适应了“微”机,抓住了“微”机的企业将是这个时代最大的赢家,小米赢了,星巴克赢了……
lnst-developers(a)lists.fedorahosted.org 参加《微信营销高级实战运营系统》,下一个赢家,就是你!
9月27深圳、10月17上海、10月18北京 ---- 3200/两人“买一赠一”,单独一人收费1980元
适合对象:企业的经营者、营销负责人、网络营销人员、企业营销策略制定者及所有营销人员
授课方式:讲师讲授 + 视频演绎 + 案例研讨 +角色扮演 + 讲师点评 + 落地工具。
咨询电话:0755-612-880-91 010-516-618-51 021-510-822-02 段小姐
培・训・收・益:
1、全面系统学习微信营销运营系统搭建,打造完整的微信电商体系
2、知晓团队规划、管理的方方面面,提升团队整体实战能力
3、从活动到互动,从引流到转化,学会把握成交关键的细节
4、数十种一线实战运营经验技巧,省时省力实现高效运营
5、运营反查快速找出问题所在,有病自医不费成本不费精力
6、落地计划书加全套实战落地工具,即学即回高效开展微信营销
讲・师・介・绍:[ 马・佳・彬 ]
微信实战应用专家、网络营销实战专家
网名:汗马,现居住广东广州。
主要成就:企业学习网微信营销高级讲师、企业学习网战略发展顾问、中山大学MBA微信营销讲师、上海交通大学
EMBA总裁班导师、中央人民广播电台经济之声时评嘉宾代表作品:《“马”道微信》、《企业实操微信八卦图》
教・育・背・景:
草根创业者,独立IT博客评论员,自媒体人,微信实战应用专家,网络营销实战专家,中央人民广播电台经济之声
时评嘉宾,《前沿讲座》特邀演讲嘉宾,单仁集团移动营销金牌讲师,中特《微信解码》专家团专家,企业学习网高级
讲师,中山大学MBA微信营销讲师;智度行销机构首席讲师。
马老师是多家知名网站的专栏作家,如:Chinaz站长之家,Donews新锐作家、速途网、艾瑞网、易观网、亿邦动力
网和最科技网等。微信营销领域专业排名前四*马老师拥有9年的互联网行业培训经验,先后从事信息咨询及广告传媒工
作。微博营销领域首次提出“灭亡论”。最早涉足研究微信营销,微信营销实战班网络培训开创者,“微信营销六步思
维法”讲师。长期担任业内多家知名IT门户站点写手。其个人博客在业内拥有比较高的知名度,已被网站运营等专业书
籍收录推荐。培训学员数以万计,马老师由于长期亲密接触网络营销一线,因此,讲解风格生动、贴近实际,更易引起
学员共鸣!是最早的微信营销研究及实践者,在微信营销领域具有完善系统的研究成果。
课・程・大・纲:
一、微信营销高级实战运营计划
1、运营目标设定(定性/定量,成功最大关键在于确立合适的目标。)
2、运营平台/工具组合(理解/筛选,正确选择平台/工具拒绝盲目。)
3、运营团队规划(招聘/架构,没有好的团队规划就干不出好的运营工作。)
二、微信营销高级实战运营执行
1、平台搭建(万丈高楼从地起,根基打错步步皆错。)
2、团队管理(团队高效管理,全面提升微信营销实战能力。)
3、内容策划(内容为王,内容创作18招,14种标题策划思路。)
4、活动策划(15种实用活动策划方式促进产品销售。)
5、互动策划(互动为皇,引导、驱动、维护粉丝关系。)
6、推广引流(全网推广18招,学会基本功一通百通。)
7、转化成交(说服力9步法,让客户心甘情愿掏钱购买。)
8、客户维护(5种客户两大管理模式,自建微信客户关系管理系统。)
三、微信营销高级实战运营技巧
1、平台运营技巧(玩转微信公众平台不可不懂的技巧。)
2、工具应用技巧(多种工具应用技巧让日常运营工作事半功倍。)
3、自媒体传播技巧(再小的企业和品牌都能建立自媒体。)
4、粉丝主动传播技巧(5招让粉丝主动传播,借助圈子传递强信任。)
四、微信营销高级实战运营反查
1、数据反查(收集、提炼、分析、总结,学会用数据指导运营。)
2、运营反查(干什么?用什么干?谁去干?怎么干?反推看全面抓细节。)
3、资源反查(自身资源投入,外部资源整合,合理规划把资源用到刀刃上。)
4、营销反查(微信营销即服务营销,客户至上不能只说到不做到。)
课后作业:《微信营销高级实战运营系统》落地计划书
附送:《微信营销高级实战运营系统》落地工具包
9 years, 6 months