blob: 1c2fce42cfda3a9f039f8d82bcfbbe050213ad68 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""ChromeOS connection manager Autotest tests covering service profiles.
This file provides functionality as described in crosbug.com/25795.
Specifically providing support for:
* The Profiles property of the Manager.
* The Entries property of the Profile.
* The DeleteEntry method of the Profile.
* The GetEntryByName method of the Profile.
"""
import logging
import optparse
import re
import sys
import dbus
import site_wlan_dbus_setup
def GetObject(kind, path):
"""Returns a DBus interface for the specified object.
Args:
kind: String containing the type of object such as "Profile" or "Service".
path: String containing the DBus path to the object.
Returns:
The DBus interface to the object.
"""
return dbus.Interface(
site_wlan_dbus_setup.bus.get_object(site_wlan_dbus_setup.FLIMFLAM, path),
site_wlan_dbus_setup.FLIMFLAM + "." + kind)
class DbusInterface(object):
"""Top-level wrapper class for objects found on the DBus."""
def __init__(self, interface):
"""Constructor DBusInterface object with an interface.
Args:
interface: A dbus.Interface object used to access the DBus on behalf of
the object.
"""
self.dbus_interface = interface
def GetProperty(self, kind):
"""Returns the value of the specified property of the DBus object.
Args:
kind: A string, like "Profiles". The property we're getting.
Returns:
The property of the given kind (see the DBus object's API documentation
to get a description of the return value for this call).
"""
return self.dbus_interface.GetProperties().get(kind)
@property
def name(self):
return "<Base Class>"
class Manager(DbusInterface):
"""Wrapper for DBus 'Manager'."""
def __init__(self):
"""Initializes using global 'manager' defined in site_wlan_dbus_setup.py.
This file needs to use site_wlan_dbus_setup.py, a file which declares a
global: manager. This class wraps access to this variable so that it's
only used here, in __init__. All other access to 'manager' is done
through this class.
"""
DbusInterface.__init__(self, site_wlan_dbus_setup.manager)
def GetProfile(self, desired=None):
"""Gets a specific Profile from the profile list.
Args:
desired: String containing the name of the desired profile. If None, use
the default profile.
Returns:
A 'Profile' object if the profile is found; 'None', otherwise.
"""
if desired:
desired_path = "/profile/%s" % desired
profiles = self.GetProperty("Profiles")
if desired_path in profiles:
return Profile(desired_path)
else:
active_path = self.GetProperty("ActiveProfile")
if active_path:
return Profile(active_path)
return None
@property
def name(self):
return "Connection-Manager"
class Profile(DbusInterface):
"""Wrapper for a Connection Manager Profile."""
class EntryIterator(object):
"""Iterates over this Profile's Entries.
NOTE: This iterator does not take kindly to the entry list changing after
the iterator is created.
"""
def __init__(self, profile):
# The Profile's "Entries" property is an array of paths to entries.
self.__profile = profile
self.__paths = self.__profile.GetProperty("Entries")
self.__path_count = len(self.__paths)
self.__index = 0
def __iter__(self):
return self
def next(self):
"""Returns a tuple of the path to the next entry and the entry."""
if self.__index >= self.__path_count:
raise StopIteration
path = self.__paths[self.__index]
self.__index += 1
entry = self.__profile.dbus_interface.GetEntry(path)
return path, entry
def __init__(self, profile_name):
"""Constructor.
Args:
profile_name: A string, possibly returned by Manager.GetProperty().
"""
DbusInterface.__init__(self, GetObject("Profile", profile_name))
def IterEntries(self):
return self.EntryIterator(self)
def GetEntryByName(self, desired):
"""Returns an entry whose Name property matches 'desired'.
Args:
desired: String containing the name of the desired entry.
Returns:
A tuple consisting of the RPC identifier for the entry and the
dictionary that is the found entry (or 'None' if not found).
"""
match = re.compile(desired + "$")
for path, entry in self.IterEntries():
name = entry["Name"]
logging.debug(" Does '%s' match '%s'", name, desired)
if name is not None and match.match(name):
logging.debug(" YES, path=%s", name)
return path, entry
logging.error("** '%s' not in ENTRIES", desired)
return None
@property
def name(self):
return self.dbus_interface.GetProperties().get("Name")
def DeleteEntry(self, desired_path):
logging.debug("Deleting entry '%s' for profile '%s'", desired_path,
self.name)
self.dbus_interface.DeleteEntry(desired_path)
class Service(DbusInterface):
"""Wrapper for the manager 'property'."""
def __init__(self, service_name):
"""Constructor.
Args:
service_name: A string, possibly returned by Manager.GetProperty().
"""
DbusInterface.__init__(self, GetObject("Service", service_name))
@property
def name(self):
return self.dbus_interface.GetProperties().get("Name")
class ProfileTest(object):
"""Contains all the top-level testing methods for Profiles."""
RETURN_FAILURE = -1
RETURN_SUCCESS = 0
def _GetProfileEntry(self, mgr, requests, ethernet_mac=None):
"""Parses "profile:xxx" and "entry:xxx" parameters from requests.
Args:
mgr: A DBus manager object.
requests: A dictionary that contains variable:value pairs (both strings).
One 'variable' can be 'profile', describing the profile to be used
(defaults to the active profile). Exactly one 'variable' is
expected to be 'entry', describing the entry inside the profile.
ethernet_mac: String containing only the 12 hex digits of the MAC address
describing the primary Ethernet interface on the current box.
Returns:
'None' on error; otherwise, returns a tuple containing profile, the
entry, and the entry's ident.
"""
# Extract the specific Profile Entry (use active profile if none
# specified).
if not requests:
return None
logging.debug("REQUESTS: %s", requests)
if "profile" in requests:
profile_name = requests.pop("profile")
profile = mgr.GetProfile(desired=profile_name)
else:
profile_name = "<ACTIVE PROFILE>"
profile = mgr.GetProfile()
if not profile:
logging.error("Couldn't find profile '%s'.", profile_name)
return None
if not requests["entry"]:
logging.error("You need to specify an entry.")
return None
# Since ethernet entry names include the MAC address, we'll extend the
# name if 'desired' is just 'ethernet'.
logging.debug("request[entry]=%s, ethernet_mac=%s", requests["entry"],
ethernet_mac)
if (requests["entry"] == "ethernet") and (ethernet_mac is not None):
entry_name = "ethernet_%s" % ethernet_mac
else:
entry_name = requests["entry"]
result = profile.GetEntryByName(entry_name)
if not result or len(result) != 2:
return None
ident, entry = result
return profile, entry, ident
def ClientCheckProfileProperties(self, mgr, options):
"""Checks that one or more profile entry properties equal what they should.
Args:
mgr: A DBus manager object.
options: An object returned from optparse.OptionParser.parse_args().
This is expected to include a value, options.param, that contains an
array of strings, each of which take the form 'variable:value'.
One 'variable' can be 'profile', describing the profile to be used
(defaults to the active profile). Exactly one 'variable' is
expected to be 'entry', describing the entry inside the profile.
The remaining 'variable' values are Entry IDs that are to be checked.
For example:
{'ethmac': '525400123456',
'command': 'ClientCheckProfileProperties',
'param': ['profile:test', 'entry:Ethernet', 'Favorite:1']}
Returns:
A value to be passed to sys.exit(): 0 for success, non-zero otherwise.
"""
if not options.param:
logging.error("You need at least one parameter.")
return ProfileTest.RETURN_FAILURE
# Convert the a:b format of the options into a dictionary.
try:
requests = dict(param.split(":", 2) for param in options.param)
except ValueError as e:
logging.error(
"All parameters in '%s' must be of the form 'param:value' (%s).",
options.param, e)
return ProfileTest.RETURN_FAILURE
ethernet_mac = options.ethmac
result = self._GetProfileEntry(mgr, requests, ethernet_mac=ethernet_mac)
if not result:
return ProfileTest.RETURN_FAILURE
profile, entry, entry_name = result
# Look for the specified parameters in the entry.
if "entry" in requests:
del requests["entry"] # Don't want to search entry for 'entry'.
for param in requests:
if param not in entry:
logging.error("Entry %s[%s] does not exist in profile %s",
entry_name, param, profile.name)
return ProfileTest.RETURN_FAILURE
elif str(entry[param]) != requests[param]:
logging.error("Entry %s[%s]==%s but should be %s.", entry_name, param,
entry[param], requests[param])
return ProfileTest.RETURN_FAILURE
else:
logging.debug("Entry %s[%s]==%s", entry_name, param, entry[param])
logging.debug("Everything matched for profile '%s', entry '%s'",
profile.name, entry["Name"])
return ProfileTest.RETURN_SUCCESS
def ClientProfileDeleteEntry(self, mgr, options):
"""Finds an entry via that entry's 'Name' property and deletes it.
Args:
mgr: A DBus manager object.
options: An object returned from optparse.OptionParser.parse_args().
This is expected to include a value, options.param, that contains an
array of strings, each of which take the form 'variable:value'.
One 'variable' can be 'profile', describing the profile to be used
(defaults to the active profile). Exactly one 'variable' is
expected to be 'entry', describing the name of the entry inside the
profile. Any remaining variables are ignored.
Returns:
A value to be passed to sys.exit(): 0 for success, non-zero otherwise.
"""
if not options.param:
logging.error("You need at check least one parameter.")
return ProfileTest.RETURN_FAILURE
# Convert the a:b format of the options into a dictionary.
try:
requests = dict(param.split(":", 2) for param in options.param)
except ValueError as e:
logging.error(
"All parameters in '%s' must be of the form 'param:value' (%s).",
options.param, e)
return ProfileTest.RETURN_FAILURE
ethernet_mac = options.ethmac
result = self._GetProfileEntry(mgr, requests, ethernet_mac=ethernet_mac)
if not result:
return ProfileTest.RETURN_FAILURE
profile, unused_entry, path = result
profile.DeleteEntry(path)
return ProfileTest.RETURN_SUCCESS
def Execute(self, argv):
"""Main entry point for the Profile-based tests.
Args:
argv: sys.argv
Returns:
A value to be passed to sys.exit(): 0 for success, non-zero otherwise.
"""
mgr = Manager()
parser = optparse.OptionParser("Usage: %prog [options...] [SSID=state...]")
parser.add_option("--command", dest="command",
help="command to run, such as "
"'ClientCheckProfileProperties' or "
"'ClientProfileDeleteEntry'.")
parser.add_option("--ethmac", dest="ethmac",
help="MAC address for the Ethernet connection (no "
"punctuation")
parser.add_option("--param", dest="param", action="append",
help="A parameter to extract and a value against "
"which to check it")
options, unused_args = parser.parse_args(argv[1:])
result = ProfileTest.RETURN_FAILURE
if not options.command:
logging.error("You need, at least, a 'command' parameter.")
return ProfileTest.RETURN_FAILURE
func = getattr(self, options.command, None)
if not func:
logging.error("Command '%s' unknown; abort test", options["command"])
return ProfileTest.RETURN_FAILURE
else:
result = func(mgr, options)
return result
def main(argv):
test = ProfileTest()
result = test.Execute(argv)
if __name__ == "__main__":
main(sys.argv)