Merge bruno-penguin-1 into vudu_moca2
diff --git a/DataModel_requirement.cfg b/DataModel_requirement.cfg
new file mode 100644
index 0000000..1f0dac6
--- /dev/null
+++ b/DataModel_requirement.cfg
@@ -0,0 +1,279 @@
+Device.DeviceInfo.AdditionalHardwareVersion
+Device.DeviceInfo.AdditionalSoftwareVersion
+Device.DeviceInfo.Description
+Device.DeviceInfo.HardwareVersion
+Device.DeviceInfo.Manufacturer
+Device.DeviceInfo.ManufacturerOUI
+Device.DeviceInfo.MemoryStatus.Free
+Device.DeviceInfo.MemoryStatus.Total
+Device.DeviceInfo.ModelName
+Device.DeviceInfo.ProcessStatus.CPUUsage
+Device.DeviceInfo.ProcessStatus.Process.{i}.CPUTime
+Device.DeviceInfo.ProcessStatus.Process.{i}.Command
+Device.DeviceInfo.ProcessStatus.Process.{i}.PID
+Device.DeviceInfo.ProcessStatus.Process.{i}.Priority
+Device.DeviceInfo.ProcessStatus.Process.{i}.Size
+Device.DeviceInfo.ProcessStatus.Process.{i}.State
+Device.DeviceInfo.ProcessStatus.ProcessNumberOfEntries
+Device.DeviceInfo.ProcessorNumberOfEntries
+Device.DeviceInfo.ProductClass
+Device.DeviceInfo.SerialNumber
+Device.DeviceInfo.SoftwareVersion
+Device.DeviceInfo.SupportedDataModelNumberOfEntries
+Device.DeviceInfo.TemperatureStatus.TemperatureSensorNumberOfEntries
+Device.DeviceInfo.UpTime
+Device.DeviceInfo.VendorConfigFileNumberOfEntries
+Device.DeviceInfo.VendorLogFileNumberOfEntries
+Device.Ethernet.Interface.{i}.DuplexMode
+Device.Ethernet.Interface.{i}.Enable
+Device.Ethernet.Interface.{i}.LastChange
+Device.Ethernet.Interface.{i}.LowerLayers
+Device.Ethernet.Interface.{i}.MACAddress
+Device.Ethernet.Interface.{i}.MaxBitRate
+Device.Ethernet.Interface.{i}.Name
+Device.Ethernet.Interface.{i}.Stats.BroadcastPacketsReceived
+Device.Ethernet.Interface.{i}.Stats.BroadcastPacketsSent
+Device.Ethernet.Interface.{i}.Stats.BytesReceived
+Device.Ethernet.Interface.{i}.Stats.BytesSent
+Device.Ethernet.Interface.{i}.Stats.DiscardPacketsReceived
+Device.Ethernet.Interface.{i}.Stats.DiscardPacketsSent
+Device.Ethernet.Interface.{i}.Stats.ErrorsReceived
+Device.Ethernet.Interface.{i}.Stats.ErrorsSent
+Device.Ethernet.Interface.{i}.Stats.MulticastPacketsReceived
+Device.Ethernet.Interface.{i}.Stats.MulticastPacketsSent
+Device.Ethernet.Interface.{i}.Stats.PacketsReceived
+Device.Ethernet.Interface.{i}.Stats.PacketsSent
+Device.Ethernet.Interface.{i}.Stats.UnicastPacketsReceived
+Device.Ethernet.Interface.{i}.Stats.UnicastPacketsSent
+Device.Ethernet.Interface.{i}.Stats.UnknownProtoPacketsReceived
+Device.Ethernet.Interface.{i}.Status
+Device.Ethernet.Interface.{i}.Upstream
+Device.Ethernet.Interface.{i}.X_CATAWAMPUS-ORG_ActualBitRate
+Device.Ethernet.Interface.{i}.X_CATAWAMPUS-ORG_ActualDuplexMode
+Device.Ethernet.InterfaceNumberOfEntries
+Device.Ethernet.LinkNumberOfEntries
+Device.Ethernet.VLANTerminationNumberOfEntries
+Device.InterfaceStackNumberOfEntries
+Device.ManagementServer.CWMPRetryIntervalMultiplier
+Device.ManagementServer.CWMPRetryMinimumWaitInterval
+Device.ManagementServer.ConnectionRequestPassword
+Device.ManagementServer.ConnectionRequestURL
+Device.ManagementServer.ConnectionRequestUsername
+Device.ManagementServer.DefaultActiveNotificationThrottle
+Device.ManagementServer.EnableCWMP
+Device.ManagementServer.ManageableDeviceNumberOfEntries
+Device.ManagementServer.ParameterKey
+Device.ManagementServer.Password
+Device.ManagementServer.PeriodicInformEnable
+Device.ManagementServer.PeriodicInformInterval
+Device.ManagementServer.PeriodicInformTime
+Device.ManagementServer.STUNEnable
+Device.ManagementServer.URL
+Device.ManagementServer.UpgradesManaged
+Device.ManagementServer.Username
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.MACAddress
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.NodeID
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.PHYRxRate
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.PHYTxRate
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.PacketAggregationCapability
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.PreferredNC
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.QAM256Capable
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.RxBcastPowerLevel
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.RxErroredAndMissedPackets
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.RxPackets
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.RxPowerLevel
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.RxSNR
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.TxBcastRate
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.TxPackets
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.TxPowerControlReduction
+Device.MoCA.Interface.{i}.AssociatedDeviceNumberOfEntries
+Device.MoCA.Interface.{i}.BackupNC
+Device.MoCA.Interface.{i}.CurrentOperFreq
+Device.MoCA.Interface.{i}.CurrentVersion
+Device.MoCA.Interface.{i}.Enable
+Device.MoCA.Interface.{i}.FirmwareVersion
+Device.MoCA.Interface.{i}.HighestVersion
+Device.MoCA.Interface.{i}.LastChange
+Device.MoCA.Interface.{i}.LastOperFreq
+Device.MoCA.Interface.{i}.LowerLayers
+Device.MoCA.Interface.{i}.MACAddress
+Device.MoCA.Interface.{i}.Name
+Device.MoCA.Interface.{i}.NetworkCoordinator
+Device.MoCA.Interface.{i}.NodeID
+Device.MoCA.Interface.{i}.PacketAggregationCapability
+Device.MoCA.Interface.{i}.PrivacyEnabled
+Device.MoCA.Interface.{i}.QAM256Capable
+Device.MoCA.Interface.{i}.Stats.BroadcastPacketsReceived
+Device.MoCA.Interface.{i}.Stats.BroadcastPacketsSent
+Device.MoCA.Interface.{i}.Stats.BytesReceived
+Device.MoCA.Interface.{i}.Stats.BytesSent
+Device.MoCA.Interface.{i}.Stats.DiscardPacketsReceived
+Device.MoCA.Interface.{i}.Stats.DiscardPacketsSent
+Device.MoCA.Interface.{i}.Stats.ErrorsReceived
+Device.MoCA.Interface.{i}.Stats.ErrorsSent
+Device.MoCA.Interface.{i}.Stats.MulticastPacketsReceived
+Device.MoCA.Interface.{i}.Stats.MulticastPacketsSent
+Device.MoCA.Interface.{i}.Stats.PacketsReceived
+Device.MoCA.Interface.{i}.Stats.PacketsSent
+Device.MoCA.Interface.{i}.Stats.UnicastPacketsReceived
+Device.MoCA.Interface.{i}.Stats.UnicastPacketsSent
+Device.MoCA.Interface.{i}.Stats.UnknownProtoPacketsReceived
+Device.MoCA.Interface.{i}.Status
+Device.MoCA.Interface.{i}.Upstream
+Device.MoCA.InterfaceNumberOfEntries
+Device.PeriodicStatistics.MaxReportSamples
+Device.PeriodicStatistics.MinSampleInterval
+Device.PeriodicStatistics.SampleSet.{i}.Enable
+Device.PeriodicStatistics.SampleSet.{i}.FetchSamples
+Device.PeriodicStatistics.SampleSet.{i}.Name
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.CalculationMode
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Enable
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Failures
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.HighThreshold
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.LowThreshold
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Reference
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SampleMode
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SampleSeconds
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SuspectData
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Values
+Device.PeriodicStatistics.SampleSet.{i}.ParameterNumberOfEntries
+Device.PeriodicStatistics.SampleSet.{i}.ReportEndTime
+Device.PeriodicStatistics.SampleSet.{i}.ReportSamples
+Device.PeriodicStatistics.SampleSet.{i}.ReportStartTime
+Device.PeriodicStatistics.SampleSet.{i}.SampleInterval
+Device.PeriodicStatistics.SampleSet.{i}.SampleSeconds
+Device.PeriodicStatistics.SampleSet.{i}.Status
+Device.PeriodicStatistics.SampleSet.{i}.TimeReference
+Device.PeriodicStatistics.SampleSetNumberOfEntries
+Device.Services.STBService.{i}.Components.FrontEnd.{i}.IP.IGMP.ClientGroup.{i}.GroupAddress
+Device.Services.STBService.{i}.Components.FrontEnd.{i}.IP.IGMP.ClientGroupNumberOfEntries
+Device.Services.STBService.{i}.Components.FrontEndNumberOfEntries
+Device.Services.STBServiceNumberOfEntries
+Device.Services.StorageServices.Capabilities.FTPCapable
+Device.Services.StorageServices.Capabilities.HTTPCapable
+Device.Services.StorageServices.Capabilities.HTTPSCapable
+Device.Services.StorageServices.Capabilities.HTTPWritable
+Device.Services.StorageServices.Capabilities.SFTPCapable
+Device.Services.StorageServices.Capabilities.SupportedFileSystemTypes
+Device.Services.StorageServices.Capabilities.SupportedNetworkProtocols
+Device.Services.StorageServices.Capabilities.SupportedRaidTypes
+Device.Services.StorageServices.Capabilities.VolumeEncryptionCapable
+Device.Services.StorageServices.Enable
+Device.Services.StorageServices.LogicalVolume.{i}.Capacity
+Device.Services.StorageServices.LogicalVolume.{i}.Enable
+Device.Services.StorageServices.LogicalVolume.{i}.FileSystem
+Device.Services.StorageServices.LogicalVolume.{i}.FolderNumberOfEntries
+Device.Services.StorageServices.LogicalVolume.{i}.Name
+Device.Services.StorageServices.LogicalVolume.{i}.Status
+Device.Services.StorageServices.LogicalVolume.{i}.ThresholdLimit
+Device.Services.StorageServices.LogicalVolume.{i}.UsedSpace
+Device.Services.StorageServices.LogicalVolume.{i}.X_CATAWAMPUS-ORG_ReadOnly
+Device.Services.StorageServices.LogicalVolumeNumberOfEntries
+Device.Services.StorageServices.PhysicalMedium.{i}.Capacity
+Device.Services.StorageServices.PhysicalMedium.{i}.ConnectionType
+Device.Services.StorageServices.PhysicalMedium.{i}.FirmwareVersion
+Device.Services.StorageServices.PhysicalMedium.{i}.Health
+Device.Services.StorageServices.PhysicalMedium.{i}.HotSwappable
+Device.Services.StorageServices.PhysicalMedium.{i}.Model
+Device.Services.StorageServices.PhysicalMedium.{i}.Name
+Device.Services.StorageServices.PhysicalMedium.{i}.Removable
+Device.Services.StorageServices.PhysicalMedium.{i}.SMARTCapable
+Device.Services.StorageServices.PhysicalMedium.{i}.SerialNumber
+Device.Services.StorageServices.PhysicalMedium.{i}.Vendor
+Device.Services.StorageServices.PhysicalMediumNumberOfEntries
+Device.Services.StorageServices.StorageArrayNumberOfEntries
+Device.Services.StorageServices.UserAccountNumberOfEntries
+Device.Services.StorageServices.UserGroupNumberOfEntries
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.BadEraseBlocks
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.CorrectedErrors
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.EraseBlockSize
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.IOSize
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.MaxEraseCount
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.Name
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.ReservedEraseBlocks
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.SubVolume.{i}.DataBytes
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.SubVolume.{i}.Name
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.SubVolume.{i}.Status
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.SubVolumeNumberOfEntries
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.TotalEraseBlocks
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.UncorrectedErrors
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMediaNumberOfEntries
+InternetGatewayDevice.DeviceInfo.AdditionalHardwareVersion
+InternetGatewayDevice.DeviceInfo.AdditionalSoftwareVersion
+InternetGatewayDevice.DeviceInfo.Description
+InternetGatewayDevice.DeviceInfo.HardwareVersion
+InternetGatewayDevice.DeviceInfo.Manufacturer
+InternetGatewayDevice.DeviceInfo.ManufacturerOUI
+InternetGatewayDevice.DeviceInfo.ModelName
+InternetGatewayDevice.DeviceInfo.ModemFirmwareVersion
+InternetGatewayDevice.DeviceInfo.ProductClass
+InternetGatewayDevice.DeviceInfo.SerialNumber
+InternetGatewayDevice.DeviceInfo.SoftwareVersion
+InternetGatewayDevice.DeviceInfo.SpecVersion
+InternetGatewayDevice.DeviceInfo.UpTime
+InternetGatewayDevice.DeviceInfo.VendorConfigFileNumberOfEntries
+InternetGatewayDevice.LANDevice.{i}.LANEthernetInterfaceNumberOfEntries
+InternetGatewayDevice.LANDevice.{i}.LANUSBInterfaceNumberOfEntries
+InternetGatewayDevice.LANDevice.{i}.LANWLANConfigurationNumberOfEntries
+InternetGatewayDevice.LANDeviceNumberOfEntries
+InternetGatewayDevice.ManagementServer.ConnectionRequestPassword
+InternetGatewayDevice.ManagementServer.ConnectionRequestURL
+InternetGatewayDevice.ManagementServer.ConnectionRequestUsername
+InternetGatewayDevice.ManagementServer.DefaultActiveNotificationThrottle
+InternetGatewayDevice.ManagementServer.EnableCWMP
+InternetGatewayDevice.ManagementServer.ParameterKey
+InternetGatewayDevice.ManagementServer.Password
+InternetGatewayDevice.ManagementServer.PeriodicInformEnable
+InternetGatewayDevice.ManagementServer.PeriodicInformInterval
+InternetGatewayDevice.ManagementServer.PeriodicInformTime
+InternetGatewayDevice.ManagementServer.URL
+InternetGatewayDevice.ManagementServer.UpgradesManaged
+InternetGatewayDevice.ManagementServer.Username
+InternetGatewayDevice.PeriodicStatistics.MaxReportSamples
+InternetGatewayDevice.PeriodicStatistics.MinSampleInterval
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Enable
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.FetchSamples
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Name
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.CalculationMode
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Enable
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Failures
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.HighThreshold
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.LowThreshold
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Reference
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SampleMode
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SampleSeconds
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SuspectData
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Values
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.ParameterNumberOfEntries
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.ReportEndTime
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.ReportSamples
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.ReportStartTime
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.SampleInterval
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.SampleSeconds
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Status
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.TimeReference
+InternetGatewayDevice.PeriodicStatistics.SampleSetNumberOfEntries
+InternetGatewayDevice.Time.CurrentLocalTime
+InternetGatewayDevice.Time.Enable
+InternetGatewayDevice.Time.LocalTimeZoneName
+InternetGatewayDevice.WANDeviceNumberOfEntries
+TraceRoute.DSCP
+TraceRoute.DataBlockSize
+TraceRoute.DiagnosticsState
+TraceRoute.Host
+TraceRoute.Interface
+TraceRoute.MaxHopCount
+TraceRoute.NumberOfTries
+TraceRoute.ResponseTime
+TraceRoute.RouteHopsNumberOfEntries
+TraceRoute.Timeout
+X_CATAWAMPUS-ORG_CATAWAMPUS.RuntimeEnvInfo
+X_GOOGLE-COM_GFIBERTV.Mailbox.Name
+X_GOOGLE-COM_GFIBERTV.Mailbox.Node
+X_GOOGLE-COM_GFIBERTV.Mailbox.NodeList
+X_GOOGLE-COM_GFIBERTV.Mailbox.Value
+X_GOOGLE-COM_GVSB.EpgPrimary
+X_GOOGLE-COM_GVSB.EpgSecondary
+X_GOOGLE-COM_GVSB.GvsbChannelLineup
+X_GOOGLE-COM_GVSB.GvsbKick
+X_GOOGLE-COM_GVSB.GvsbServer
diff --git a/acs.py b/acs.py
new file mode 100755
index 0000000..8725e02
--- /dev/null
+++ b/acs.py
@@ -0,0 +1,183 @@
+#!/usr/bin/python
+# Copyright 2012 Google Inc. All Rights Reserved.
+
+"""This File executes create/modify/delete operations on ACS datastore.
+
+This class is used to interact with scripts that directly modifies
+ACS datastore configuration. These ACS related actions may require
+google3 dependencies
+"""
+
+__author__ = 'Lehan Meng (lmeng@google.com)'
+
+
+import os
+import re
+
+
+class ACS(object):
+  """This class executes operations that change Device profiles on ACS."""
+
+  def __init__(self, expected_version=None, **kwargs):
+    """Constructor for this Class."""
+    self.acs_info = {
+        'url': None,
+        # expected image version ID on ACS
+        'imageVersion': expected_version,
+        'acs_path': None,
+        'outfile': 'cmdOutput.txt'}
+    for s in kwargs:
+      self.acs_info[s] = kwargs[s]
+
+  def SetLogging(self, log):
+    """Setup the logging file(s)."""
+    self.log = log
+
+  def SetImageVersion(self, expected_version):
+    """Set the expected image version that needs to be configured at ACS.
+
+    Args:
+      expected_version: the version string of the expected image.
+    """
+    m = re.match('\d+', expected_version)
+    if m is None:
+      info = self.log.CreateErrorInfo(
+          'critical',
+          'Error! Please use software id as expected image version value')
+      self.log.SendLine(None, info)
+    else:
+      self.acs_info['imageVersion'] = expected_version
+
+  def ParseAppIDFromACSURL(self, url='https://acs.e2e.gfsvc.com/cwmp'):
+    """Parse app_id from ACS instance host name."""
+    pass
+
+  def SaveConfiguration(self, profile_id, version_id, app_id,
+                        host='https://acs.e2e.gfsvc.com/cwmp'):
+    """Run the ACS configuration script from Google3 location."""
+    if not version_id:
+      v_id = self.acs_info['imageVersion']
+    else:
+      v_id = version_id
+
+    cur_dir = os.getcwd()
+    print '============' + cur_dir
+    # the acs_path is the google3 repository where upgrade_test.py
+    # locates. This test depends on it to modify ACS datastore.
+    os.chdir(self.acs_info['acs_path'])
+    print '============' + os.getcwd()
+    os.system(
+        'blaze test --notest_loasd --test_arg=--imageVersion='
+        + v_id + ' --test_arg=--profile_id='
+        + profile_id + ' --test_arg=--app_id=' + app_id
+        + ' --test_arg=--host=' + host
+        + ' --test_strategy=local :upgrade_test ')
+    os.chdir(cur_dir)
+    print '============' + os.getcwd()
+
+  def StubbyClientCall(self, cmd=None):
+    """This is the nbi_client stubby call to get parameter names and values."""
+    p = os.popen(cmd, 'r')
+    log_list = []
+    while 1:
+      line = p.readline()
+      if not line: break
+      log_list.append(line.strip())
+    return log_list
+
+  def ParseParameterAccessStates(self, cmd_list):
+    """Find out if a parameter is 'writable' or 'read-write'.
+
+    This method process the nbi_client getParameterNames() call result
+    to find out the access state of the parameters.
+    Args:
+      cmd_list: the getParameterNames() call returned result list
+    Returns:
+      a dictionary: parameter name as index, and its access state
+      as value.
+    """
+    if ('FAILED' in cmd_list[0]) or (not 'SUCCESS' in cmd_list[0]):
+      info = self.log.CreateErrorInfo(
+          'Critical', 'nbi_clienet call failed. ' + str(cmd_list[0]))
+      self.log.SendLine(None, info)
+      return False
+
+    para_dict = {}
+    for line in cmd_list:
+      if 'name:' in line:
+        name = re.sub('.*name:\s*\"(.*)\".*', r'\1', line)
+        index = cmd_list.index(line)+1
+        access = re.sub('.*writable:\s*([a-zA-Z]+)', r'\1', cmd_list[index])
+        para_dict.update({name: access})
+    return para_dict
+
+  def CheckCmdCall(self, cmd_list):
+    """Check if the RPC call is a success or failure."""
+    if ('FAILED' in cmd_list[0]) or (not 'SUCCESS' in cmd_list[0]):
+      info = self.log.CreateErrorInfo(
+          'Critical', 'nbi_client call failed. ' + str(cmd_list[0]))
+      self.log.SendLine(None, info)
+      return False
+    else:
+      return True
+
+  def ParseManagementURL(self, cmd_list):
+    """Parse Management URL from GetParameterValues query reply message.
+
+    Reply message is in the following format:
+    #########################################################
+    SUCCESS! Response: parameter_value_list {
+    parameter_value_struct {
+    name: "InternetGatewayDevice.ManagementServer.URL"
+    string_value: "https://acs.e2e.gfsvc.com/cwmp"
+       }
+    }
+    #########################################################
+
+    Args:
+      cmd_list: the ACS url information obtained from device log or file
+    Returns:
+      returns the management url found, or return false if url not found
+    """
+    if not cmd_list:
+      return False
+    for line in cmd_list:
+      if ('name:' in line and
+          'InternetGatewayDevice.ManagementServer.URL' in line):
+        s = cmd_list[cmd_list.index(line)+1]
+        m = re.search('http[s]?://(\w*\.)+com/cwmp', s)
+        if not m:
+          info = self.log.CreateErrorInfo(
+              'Warning', 'No management server URL found in GetParameterValues '
+              'query reply.')
+          self.log.SendLine(None, info)
+          return False
+        else:
+          return m.group(0)
+
+  def ParseParameterNames(self, cmd_list, replace_index=False):
+    """Parse the get values RPC call response, retrieve parameter name list.
+
+    Args:
+      cmd_list: the command reply message from nbi_client
+      replace_index: if True, the index digits in parameter names will be
+                     replaced with '.{i}.'
+    Returns:
+      return the parsed parameter name list
+    """
+    if not self.CheckCmdCall(cmd_list):
+      return False
+
+    chk_list = []
+    for line in cmd_list:
+      if 'name:' in line:
+        output = re.sub('.*\"(.*)\".*', r'\1', line)
+        if replace_index:
+          output = re.sub(r'(\.)[\d]+(\.)', r'\1{i}\2', output)
+        if chk_list.count(output) < 1:
+          chk_list.append(output)
+    return chk_list
+
+
+if __name__ == '__main__':
+  pass
diff --git a/basic_TR69.py b/basic_TR69.py
new file mode 100644
index 0000000..0b9bfae
--- /dev/null
+++ b/basic_TR69.py
@@ -0,0 +1,651 @@
+#!/usr/bin/python
+# Copyright 2012 Google Inc. All Rights Reserved.
+
+"""This class implements some basic TR069 test cases.
+
+   This class extends the TestCase Class.
+   This file implements some basic TR069 test cases to verify Bruno management
+   features. In the following test cases, only those that are defined in
+   'config.cfg' file will be executed.
+
+   Basic TR069 test cases include the following:
+   testID: 11865129  7.6.1 ACS Connection via IPv6
+   testID: 11721375  7.6.10 Verify there is a way to reset to factory default
+                            if user modifies the local configuration
+                            '/etc/init.d/S05factoryreset later'
+   testID: 14319146  7.6.19 Default ACS URL
+   testID: 11721371  7.6.2 ACS URL Pass to Bruno via Moca
+   testID: 14312009  7.6.21 Restrict the ability to locally configure ACS URL
+   testID: 14186028  7.6.22 Security of the link between the DHCPv6 server
+                            and the CPE
+   testID: 14312010  7.6.23 CPE Connection Initiation
+   testID: 14247012  7.6.24.1 Support of Connection Request notification
+                              mechanism
+   testID: 14186029  7.6.24.2 InternetGatewayDevice.ManagementServer.
+                              ConnectionRequestURL
+   testID: 14312011  7.6.24.3 InternetGatewayDevice.ManagementServer.
+                              ConnectionRequestURL format
+   testID: 14293012  7.6.24.4 No data is conveyed in the Connection
+                              Request HTTP Get notification
+   testID: 14165037  7.6.24.5 Restrict the number of Connection Request
+                              notifications
+   testID: 14315010  7.6.24.6 Connection Request Notification does not
+                              terminate current session
+   testID: 14247013  7.6.25.1 MaxEnvelopes
+   testID: 14312012  7.6.25.2 Cookie support (optional)
+   testID: 14293013  7.6.25.4 Authentication
+   testID: 14311008  7.6.26.1 SOAP namespace identifier
+   testID: 14293014  7.6.26.2 SOAP Fault element
+   testID: 14311009  7.6.26.3 SOAP future extensibility
+   testID: 14186030  7.6.27 Fault Handling
+   testID: 14319148  7.6.27.1 SOAP requests respond
+   testID: 14266497  7.6.27.2 Session Termination
+   testID: 14312013  7.6.28 Correctly handle concurrent downloads in catawampus
+   testID: 11874098  7.6.3.1 TLS (1.2)/SSL based Transport for TR-069 on IPv6
+   testID: 14315007  7.6.3.2 Support for encryption algorithms
+   testID: 14247014  7.6.32 Persist TR-069 config across reboots
+   testID: 14293015  7.6.33 Send notification when disk is full
+   testID: 14293016  7.6.34 Support for multiple SSIDs in WLANConfiguration
+   testID: 11865130  7.6.4 TR069-ACS Validation
+   testID: 11722370  7.6.5 TR069-Inform Request
+   testID: 11874103  7.6.7 Digest Authentication
+   testID: 11843139  7.6.8.1 Support of RPCs: REBOOT
+"""
+
+__author__ = 'Lehan Meng (lmeng@google.com)'
+
+
+import os
+import re
+import time
+
+from IPy import IP
+
+import device
+import ssh
+import testCase
+
+
+class BasicTR069Test(testCase.TestCase):
+  """Class for test cases: Bruno basic TR069 features.
+
+  A range of basic TR069 test cases are defined in this Class
+  """
+
+  def Init(self):
+    """Initialize the networking feature tests."""
+    # Get all device information.
+    info = self.log.CreateResultInfo(
+        '---', 'Networking test verification started...')
+    self.log.SendLine(self.test_info, info)
+    self.bruno = device.Bruno(user=self.params['user'],
+                              addr=self.params['addr'],
+                              bruno_prompt=self.params['bruno_prompt'],
+                              addr_ipv6=self.params['addr_ipv6'])
+    self.bruno.SetLogging(self.log)
+    if not self.p_ssh:
+      self.p_ssh = ssh.SSH(user=self.params['user'],
+                           addr=self.params['addr'],
+                           bruno_prompt=self.params['bruno_prompt'],
+                           addr_ipv6=self.params['addr_ipv6'])
+    self.bruno.dev_ssh = self.p_ssh
+    self.p_ssh.SetLogging(self.log)
+
+    self.__delay_5_sec = 5
+    self.__delay_5_min = 300
+    self.__delay_15_min = 900
+    return
+
+  def SshTunnel(self):
+    """Create ssh tunnel for the test."""
+    self.p_ssh.SshRetry(5, 15)
+    return
+
+  def VerifyDuplicatedAddress(self, ip_addr):
+    """Verify device can detect duplicated ipv6 address existing in network.
+
+    Set a duplicated ip address on the device.
+    Verify that the device can detect duplicated ip address
+
+    Args:
+      ip_addr: the duplicated address
+    Returns:
+      True: if duplicated detected
+      False: if duplicated address is not detected
+    """
+    time_stamp = self.bruno.GetTimeStamp()
+    self.bruno.dev_ssh.SendCmd('ip -6 addr add ' + ip_addr + '/64 dev br0')
+    time.sleep(1)
+    self.bruno.dev_ssh.SendCmd('dmesg')
+    log_list = self.bruno.dev_ssh.GetCmdOutput()
+
+    pattern_line = (
+      '\[\d*\.\d{3}\]\s*\w*\d:\s* IPv6 duplicate address\s*('
+      + ip_addr + ')\s*[/\d+]*\s*detected!')
+    self.bruno.dev_ssh.SendCmd('ip -6 addr del ' + ip_addr + '/64 dev br0')
+
+    if self.bruno.FindLineInLog(log_list, time_stamp, pattern_line):
+      info = self.log.CreateResultInfo('Pass', 'Duplicated IPv6 address '
+                                       'detected: ' + ip_addr)
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateResultInfo('Failed',
+                                       'Duplicated IPv6 address not detected: '
+                                       + ip_addr + '. Verification Failed!')
+      self.log.SendLine(self.test_info, info)
+    #[132344.324] br0: IPv6 duplicate address fe80:... detected!
+
+  def VerifyExpectedVersion(self):
+    """Verify if device is running on expected software version."""
+    if not self.bruno.VerifyCurrentVersion(self.params['expected_version']):
+      info = self.log.CreateErrorInfo('critical',
+                                      'Device software version is not expected!'
+                                      ' Exit!')
+      self.log.SendLine(self.test_info, info)
+      os.sys.exit(1)
+
+  def VerifyACSWithIPv6(self):
+    """Test ID: 11865129 - Verify IPv6 connection to ACS.
+
+    To verify device connects to ACS via IPv6, use tcpdump to capture
+    network packets, filtered with source IPv6 address, and destination
+    IPv6 address of Bruno and ACS.
+
+    tcpdump filter:
+      1. source address: device address
+         destination address: ACS address
+      2. source address: ACS address
+         destination address: device address
+
+    if packets on both directions are found, test succeeds.
+    Otherwise, test failed.
+    """
+    info = self.log.CreateProgressInfo(
+          '10%', 'Verifying current image version, expected: '
+          + self.params['expected_version'])
+    self.log.SendLine(self.test_info, info)
+    self.VerifyExpectedVersion()
+
+    # capture 10 packets on both directions.
+    # capture length: 100 bytes
+    packet_to_catch = '5'
+    bytes_to_catch = '100'
+    interface_to_catch = 'br0'
+    tcpdump_command = ('tcpdump -i ' + interface_to_catch + ' -c '
+                       + packet_to_catch + ' -s ' + bytes_to_catch
+                       + ' \'(((src ' + self.params['addr_ipv6']
+                       + ') and (dst ' + self.params['acs_ipv6']
+                       + ')) or ((src ' + self.params['acs_ipv6']
+                       + ') and (dst ' + self.params['addr_ipv6']
+                       + ')))\'')
+    info = self.log.CreateProgressInfo(
+          '30%', 'Calling tcpdump to capture device traffic to ACS server: '
+          'capturing on interface: ' + interface_to_catch
+          + ' Catch packets: ' + packet_to_catch)
+    self.log.SendLine(self.test_info, info)
+    self.p_ssh.SendCmd(tcpdump_command, 180)
+    info = self.log.CreateProgressInfo(
+          '60%', 'Packets captured. Analyzing...')
+    self.log.SendLine(self.test_info, info)
+    tcpdump_result = self.p_ssh.GetCmdOutput()
+    tcpdump_result.reverse()
+    if self.CheckACSIPv6Connection(tcpdump_result):
+      info = self.log.CreateProgressInfo(
+          '100%', 'TR069 traffic verified: Device connected to ACS via IPv6')
+      self.log.SendLine(self.test_info, info)
+      info = self.log.CreateResultInfo(
+          'Pass', 'TR069 traffic verified: Device connected to ACS via IPv6')
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateResultInfo(
+          'Failed', 'TR069 traffic verified: No IPv6 connection detected.')
+      self.log.SendLine(self.test_info, info)
+
+  def ParseDumpMsgToPackets(self, tcpdump_result):
+    """Parse tcpdump dump messages information to packets.
+
+    tcpdump result (for example):
+    14:24:36.887936 00:1a:11:30:64:e6 (oui Unknown) > a8:d0:e5:a1:e3:02    L1
+      (oui Unknown), ethertype IPv6 (0x86dd), length 86:                   L2
+    0x0000:  6000 0000 0020 0640 2605 a601 fe00 fd18  `......@&.......     L3
+    0x0010:  021a 11ff fe30 64e6 2001 4860 8005 0000  .....0d...H`....     L4
+    0x0020:  0000 0000 0000 008d e2e8 01bb 73e4 0dc8  ............s...     L5
+    0x0030:  a1bf d109 8011 0429 276b 0000 0101 080a  .......)'k......     L6
+    0x0040:  0389 b875 d29d 2401                      ...u..$.             L7
+    14:24:36.912863 a8:d0:e5:a1:e3:02 (oui Unknown) > 00:1a:11:30:64:e6    L8
+      (oui Unknown), ethertype IPv6 (0x86dd), length 86:                   L9
+    0x0000:  6000 0000 0020 0634 2001 4860 8005 0000  `......4..H`....     L10
+    0x0010:  0000 0000 0000 008d 2605 a601 fe00 fd18  ........&.......     L11
+    0x0020:  021a 11ff fe30 64e6 01bb e2e8 a1bf d109  .....0d.........     L12
+    0x0030:  73e4 0dc9 8011 05a8 6bdc 0000 0101 080a  s.......k.......     L13
+    0x0040:  d29d 763c 0389 b875                      ..v<...u             L14
+
+    This method will parse the dump message to two packets dump:
+    (new packet starts with time stamp: 14:24:36.912863)
+    packet_list[0]: line L1 ~ line L7
+    packet_list[1]: line L8 ~ line L14
+
+    Args:
+      tcpdump_result: raw dump message from tcpdump
+    Returns:
+      packet_list
+    """
+    packet_list = []
+    packet = []
+    for line in tcpdump_result:
+      m = re.search('^\s*\d{2}:\d{2}:\d{2}\.\d+\s*', line)
+      if m:
+        # new packet found:
+        packet = []
+        packet.append(line)
+        index = tcpdump_result.index(line)
+        while index+1 < len(tcpdump_result):
+          packet_line = tcpdump_result[index+1]
+          m = re.search('^\s*\d{2}:\d{2}:\d{2}\.\d+\s*', packet_line)
+          if not m:
+            # not start of next packet
+            packet.append(packet_line)
+            index +=1
+          else:
+            # next packet starts
+            packet_list.append(packet)
+            break
+    return packet_list
+
+  def FindSrcAddrFromTcpdump(self, packet):
+    """Parse source IPv6 address from tcpdump message.
+
+    Args:
+      packet: tcpdump dump message
+    Returns:
+      source address in packet dump message
+    """
+    src_addr = None
+    for line in packet:
+      m = re.search('^\s*0x0000:\s*([\da-f]{4})\s*([\da-f]{4})\s*([\da-f]{4})'
+                    '\s*([\da-f]{4})\s*([\da-f]{4})\s*([\da-f]{4})'
+                    '\s*([\da-f]{4})\s*([\da-f]{4})\s*.*', line)
+      if m:
+        # first line of packet data:
+        src_addr = (m.group(5) + ':' + m.group(6) + ':' + m.group(7)
+                    + ':' + m.group(8)) + ':'
+        index = packet.index(line)
+        m = re.search('^\s*0x0010:\s*([\da-f]{4})\s*([\da-f]{4})\s*([\da-f]{4})'
+                      '\s*([\da-f]{4})\s*([\da-f]{4})\s*([\da-f]{4})'
+                      '\s*([\da-f]{4})\s*([\da-f]{4})\s*.*', packet[index+1])
+        src_addr += (m.group(1) + ':' + m.group(2) + ':' + m.group(3)
+                     + ':' + m.group(4))
+        break
+    return src_addr
+
+  def FindDstAddrFromTcpdump(self, packet):
+    """Parse destination IPv6 address from tcpdump message.
+
+    Args:
+      packet: tcpdump dump message
+    Returns:
+      source address in packet dump message
+    """
+    dst_addr = None
+    for line in packet:
+      m = re.search('^\s*0x0010:\s*([\da-f]{4})\s*([\da-f]{4})\s*([\da-f]{4})'
+                    '\s*([\da-f]{4})\s*([\da-f]{4})\s*([\da-f]{4})'
+                    '\s*([\da-f]{4})\s*([\da-f]{4})\s*.*', line)
+      if m:
+        # first line of packet data:
+        dst_addr = (m.group(5) + ':' + m.group(6) + ':' + m.group(7)
+                    + ':' + m.group(8)) + ':'
+        index = packet.index(line)
+        m = re.search('^\s*0x0020:\s*([\da-f]{4})\s*([\da-f]{4})\s*([\da-f]{4})'
+                      '\s*([\da-f]{4})\s*([\da-f]{4})\s*([\da-f]{4})'
+                      '\s*([\da-f]{4})\s*([\da-f]{4})\s*.*', packet[index+1])
+        dst_addr += (m.group(1) + ':' + m.group(2) + ':' + m.group(3)
+                     + ':' + m.group(4))
+        break
+    return dst_addr
+
+  def CheckACSIPv6Connection(self, tcpdump_result):
+    """Examine captured packets, find IPv6 communication between ACS and device.
+
+    Args:
+      tcpdump_result: tcpdump result: captured packets message dump
+    Returns:
+      True: if bi-directional communication found from packet list.
+      False: otherwise
+    """
+    packet_list = self.ParseDumpMsgToPackets(tcpdump_result)
+    dev_to_acs = False
+    acs_to_dev = False
+    for pacekt in packet_list:
+      src_addr = IP(self.FindSrcAddrFromTcpdump(pacekt))
+      dst_addr = IP(self.FindDstAddrFromTcpdump(pacekt))
+      if (src_addr == IP(self.params['addr_ipv6'])
+          and dst_addr == IP(self.params['acs_ipv6'])):
+        dev_to_acs = True
+      if (dst_addr == IP(self.params['addr_ipv6'])
+          and src_addr == IP(self.params['acs_ipv6'])):
+        acs_to_dev = True
+
+    if dev_to_acs and acs_to_dev:
+      return True
+    else:
+      return False
+
+  def VerifyFactoryReset(self):
+    """Test case: 11721375, Verify the factory reset function of device.
+
+    Verify factory reset according to the following conditions:
+    - version
+    - address
+    - cat /etc/ntpd.conf
+    - cat /etc/resolv.conf
+    - cat /tmp/gvsbhost
+    - ls /tmp/gvsb*
+    - cat /tmp/cwmp/acs_url
+    - ls -l /var/media
+    - ls -l /rw/sage
+
+    This test will need to manually reset the device using reset button.
+    """
+    test_failed = False
+    info = self.log.CreateProgressInfo(
+          '10%', 'Verifying current image version, expected: '
+          + self.params['expected_version'])
+    self.log.SendLine(self.test_info, info)
+    self.VerifyExpectedVersion()
+
+    # get file time information:
+    wiz_bin_date = None
+    self.p_ssh.SendCmd('ls -l /rw/sage')
+    cmd_list = self.p_ssh.GetCmdOutput()
+    for line in cmd_list:
+      if 'Wiz.bin' in line:
+        m = re.search('.*(\d{4}-\d{2}-\d{2}\s*\d+:\d+).*', line)
+        if m:
+          wiz_bin_date = m.group(1)
+
+    info = self.log.CreateProgressInfo(
+          '20%', 'Please reset the device. '
+          'Hold down reset button for 5 seconds...')
+    self.log.SendLine(self.test_info, info)
+
+    info = self.log.CreateProgressInfo(
+          '30%', 'Wait ' + str(self.__delay_5_min)
+          + ' for device to reset and reboot...')
+    self.log.SendLine(self.test_info, info)
+    time.sleep(self.__delay_5_min)
+
+    info = self.log.CreateProgressInfo(
+          '40%', 'Connecting to device...')
+    self.log.SendLine(self.test_info, info)
+    self.SshTunnel()
+
+    addr = self.bruno.GetIPv4Address()
+    if not addr:
+      info = self.log.CreateErrorInfo('critical',
+                                      'Error! No IPv4 address found!')
+      self.log.SendLine(self.test_info, info)
+      test_failed = True
+    else:
+      info = self.log.CreateProgressInfo(
+          '50%', 'Checking device IPv4 address succeed: IPv4: '
+          + addr)
+      self.log.SendLine(self.test_info, info)
+
+    addr_v6 = self.bruno.GetIPv6Address()
+    if not addr_v6:
+      info = self.log.CreateErrorInfo('warning', 'No IPv6 address found!')
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateProgressInfo(
+          '55%', 'Checking device IPv6 address succeed: IPv6: '
+          + addr_v6[0])
+      self.log.SendLine(self.test_info, info)
+
+    if self.bruno.GetProductClass() == 'GFMS100':
+      # fat bruno, check Wiz.bin file:
+      found_file = False
+      self.p_ssh.SendCmd('ls -l /rw/sage')
+      cmd_list = self.p_ssh.GetCmdOutput()
+      for line in cmd_list:
+        if 'Wiz.bin' in line:
+          found_file = True
+          m = re.search('.*(\d{4}-\d{2}-\d{2}\s*\d+:\d+).*', line)
+          new_date = m.group(1)
+          if new_date == wiz_bin_date:
+            # if file is not updated
+            test_failed = True
+            info = self.log.CreateErrorInfo(
+                'warning', 'Wiz.bin not updated: '
+                'file date before reset: ' + wiz_bin_date
+                + ' new file date: ' + new_date)
+            self.log.SendLine(self.test_info, info)
+          else:
+            info = self.log.CreateProgressInfo(
+                '60%', 'Checking Wiz.bin file succeed: new file created at: '
+                + new_date)
+            self.log.SendLine(self.test_info, info)
+          break
+      if not found_file:
+        test_failed = True
+        info = self.log.CreateErrorInfo(
+            'critical', 'No Wiz.bin file found after reset!')
+        self.log.SendLine(self.test_info, info)
+
+    # verify ntpd.conf
+    ts = self.bruno.GetTimeServer()
+    if not ts:
+      test_failed = True
+      info = self.log.CreateErrorInfo(
+          'critical', 'No time server found in file /etc/ntpd.conf!')
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateProgressInfo(
+          '65%', 'Checking time server (ntpd.conf) succeed: Timeserver: '
+          + ts)
+      self.log.SendLine(self.test_info, info)
+
+    # verify resolv.conf
+    dns = self.bruno.GetDNSServer()
+    if not dns:
+      test_failed = True
+      info = self.log.CreateErrorInfo(
+          'critical', 'No DNS server found in file /etc/resolv.conf!')
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateProgressInfo(
+          '70%', 'Checking DNS server (resolv.conf) succeed: DNS: '
+          + dns)
+      self.log.SendLine(self.test_info, info)
+
+    # check gvsb:
+    gvsb = self.bruno.GetGVSBHost().strip()
+    if not gvsb or (gvsb != self.params['gvsb_host']):
+      test_failed = True
+      info = self.log.CreateErrorInfo(
+          'critical', 'GVSB host not found or not matching expected host!')
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateProgressInfo(
+          '75%', 'Checking GVSB server (gvsbhost) succeed: URL: '
+          + gvsb)
+      self.log.SendLine(self.test_info, info)
+
+    # check gvsb files:
+    self.bruno.dev_ssh.SendCmd('ls /tmp/gvsb*')
+    cmd_list = self.bruno.dev_ssh.GetCmdOutput()
+    gvsb_channel = False
+    gvsb_kick = False
+    gvsb_host = False
+    for line in cmd_list:
+      if 'gvsbhost' in line:
+        gvsb_host = True
+      elif 'gvsbchannel' in line:
+        gvsb_channel = True
+      elif 'gvsbkick' in line:
+        gvsb_kick = True
+    if not gvsb_channel or not gvsb_kick or not gvsb_host:
+      test_failed = True
+      info = self.log.CreateErrorInfo(
+          'critical', 'GVSB files missing! Check /tmp/for gvsb files')
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateProgressInfo(
+          '80%', 'Checking GVSB files (/tmp/gvsb*) succeed. Files found: '
+          'gvsbhost, gvsbchannel, gvsbkick.')
+      self.log.SendLine(self.test_info, info)
+
+    # verify acs url:
+    if self.bruno.GetACSUrl() != self.params['acs_url']:
+      test_failed = True
+      info = self.log.CreateErrorInfo(
+          'critical', 'ACS URL is not expected! Expected URL: '
+          + self.params['acs_url'])
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateProgressInfo(
+          '85%', 'Checking ACS URL (/tmp/cwmp/acs_url) succeed. URL: '
+          + self.params['acs_url'])
+      self.log.SendLine(self.test_info, info)
+
+    # verify sage properties
+    self.bruno.dev_ssh.SendCmd('ls -l /rw/sage')
+    cmd_list = self.bruno.dev_ssh.GetCmdOutput()
+    sage_prop = False
+    sage_prop_backup = False
+    for line in cmd_list:
+      if 'Sage.properties.autobackup' in line:
+        sage_prop_backup = True
+      elif 'Sage.properties' in line:
+        sage_prop = True
+    if not sage_prop or not sage_prop_backup:
+      test_failed = True
+      info = self.log.CreateErrorInfo(
+          'critical', 'Missing file in /rw/sage folder.')
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateProgressInfo(
+          '90%', 'Checking sage properties files (/rw/sage/) succeed.')
+      self.log.SendLine(self.test_info, info)
+
+    if test_failed:
+      info = self.log.CreateResultInfo(
+          'Failed', 'Device configuration not '
+          'restored completely after factory reset!')
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateResultInfo(
+          'Pass', 'Device configuration '
+          'successfully restored after factory reset.')
+      self.log.SendLine(self.test_info, info)
+      info = self.log.CreateProgressInfo(
+          '100%', 'Device configuration '
+          'successfully restored after factory reset.')
+      self.log.SendLine(self.test_info, info)
+
+  def VerifyDefaultACSURL(self):
+    """Test case: 14319146. This method verifies default ACS URL on device."""
+    # this test needs to disable the Ethernet port, therefore can not
+    # be automated via ssh tunnel
+    print 'pass this test case'
+
+  def Run(self):
+    """Run the test case."""
+    ####### Add your code here -- BEGIN #######
+    print 'Test Started...'
+
+    self.Init()
+    time.sleep(2)  # time delay between commands issued to Device
+    self.SshTunnel()
+    info = self.log.CreateProgressInfo(
+        '5%', 'SSH session to device successfully established!')
+    self.log.SendLine(self.test_info, info)
+
+    if self.test_info['testID'] == '11865129':
+      self.VerifyACSWithIPv6()
+    elif self.test_info['testID'] == '11721375':
+      self.VerifyFactoryReset()
+    elif self.test_info['testID'] == '14319146':
+      self.VerifyDefaultACSURL()
+    elif self.test_info['testID'] == '11721371':
+      print 'run test'
+    elif self.test_info['testID'] == '14312009':
+      print 'run test'
+    elif self.test_info['testID'] == '14186028':
+      print 'run test'
+    elif self.test_info['testID'] == '14312010':
+      print 'run test'
+    elif self.test_info['testID'] == '14247012':
+      print 'run test'
+    elif self.test_info['testID'] == '14186029':
+      print 'run test'
+    elif self.test_info['testID'] == '14312011':
+      print 'run test'
+    elif self.test_info['testID'] == '14293012':
+      print 'run test'
+    elif self.test_info['testID'] == '14165037':
+      print 'run test'
+    elif self.test_info['testID'] == '14315010':
+      print 'run test'
+    elif self.test_info['testID'] == '14247013':
+      print 'run test'
+    elif self.test_info['testID'] == '14312012':
+      print 'run test'
+    elif self.test_info['testID'] == '14293013':
+      print 'run test'
+    elif self.test_info['testID'] == '14311008':
+      print 'run test'
+    elif self.test_info['testID'] == '14293014':
+      print 'run test'
+    elif self.test_info['testID'] == '14311009':
+      print 'run test'
+    elif self.test_info['testID'] == '14186030':
+      print 'run test'
+    elif self.test_info['testID'] == '14319148':
+      print 'run test'
+    elif self.test_info['testID'] == '14266497':
+      print 'run test'
+    elif self.test_info['testID'] == '14312013':
+      print 'run test'
+    elif self.test_info['testID'] == '11874098':
+      print 'run test'
+    elif self.test_info['testID'] == '14315007':
+      print 'run test'
+    elif self.test_info['testID'] == '14247014':
+      print 'run test'
+    elif self.test_info['testID'] == '14293015':
+      print 'run test'
+    elif self.test_info['testID'] == '14293016':
+      print 'run test'
+    elif self.test_info['testID'] == '11865130':
+      print 'run test'
+    elif self.test_info['testID'] == '11722370':
+      print 'run test'
+    elif self.test_info['testID'] == '11874103':
+      print 'run test'
+    elif self.test_info['testID'] == '11843139':
+      print 'run test'
+    else:
+      print 'do nothing...'
+
+    print 'Test Completed...'
+    ####### Add your code here -- END   #######
+
+  def Destructor(self):
+    """This is the destructor function to call for the user extened class."""
+    self.p_ssh.close()
+
+if __name__ == '__main__':
+  # Test cases defined in this file:
+  test_defined = ['11865129', '11721375', '14319146', '11721371',
+                  '14312009', '14186028', '14312010', '14247012', '14186029',
+                  '14312011', '14293012', '14165037', '14315010', '14247013',
+                  '14312012', '14293013', '14311008', '14293014', '14311009',
+                  '14186030', '14319148', '14266497', '14312013', '11874098',
+                  '14315007', '14247014', '14293015', '14293016', '11865130',
+                  '11722370', '11874103', '11843139']
+  # To execute test cases that defined in config file:
+  test_to_execute = testCase.TestCase.FindTestCaseToExecute('config.cfg')
+  for test_id in test_to_execute:
+    if test_id in test_defined:
+      test = BasicTR069Test(testID=test_id)
diff --git a/basic_network_testcases.py b/basic_network_testcases.py
new file mode 100644
index 0000000..2979227
--- /dev/null
+++ b/basic_network_testcases.py
@@ -0,0 +1,230 @@
+#!/usr/bin/python
+# Copyright 2012 Google Inc. All Rights Reserved.
+
+"""This class implements some basic networking test cases.
+
+   This class extends the TestCase Class.
+   This file implements some test cases to verify Bruno basic networking
+   features. In the following test cases, only those that are defined in
+   'config.cfg' file will be executed.
+
+   Feature test cases include the following:
+   testID: 11874111   7.2.12 IPv6 Duplicate Address Detection
+   testID: 11724363   7.2.16 Ethernet/Moca Traffic Overloading
+   testID: 11843145   7.2.17 VLAN Pass Through
+   testID: 11723379   7.2.18 VLAN Tagging over Home Owner WiFi
+   testID: 11722378   7.2.19 VLAN Tagging over Guest WiFi
+   testID: 11721378   7.2.2 Verify BRUNO (connected to BRUNO-IS) can obtain
+                            IPv4 address via RG's DHCP service
+   testID: 11792173   7.2.20 VLAN Tag Removal
+   testID: 11721382   7.2.21 IPv6 Verify subnet with /56, /64 length
+   testID: 11843141   7.2.5 Verify client can get correct IPv4 address if
+                            the RG changes DHCP address pool
+   testID: 11721379   7.2.7 Verify time offset information obtained via
+                            DHCP option2
+"""
+
+__author__ = 'Lehan Meng (lmeng@google.com)'
+
+
+import re
+import time
+
+import device
+import ip
+import ssh
+import testCase
+
+
+class BasicNetworkingTest(testCase.TestCase):
+  """Class for test cases of Bruno networking features.
+
+      Configuration Parameters:
+      testID: 11874111/11724363/11843145/11723379
+              /11722378/11721378/11792173/11721382/
+              /11843141/11721379
+      addr = Bruno's IP address
+      user = root
+      pwd = google
+      bruno_prompt = gfibertv#
+      file = cmdOutput.txt
+      title = Basic_Networking
+  """
+
+  def Init(self):
+    """Initialize the networking feature tests."""
+    # Get all device information.
+    info = self.log.CreateResultInfo(
+        '---', 'Networking test verification started...')
+    self.log.SendLine(self.test_info, info)
+    self.bruno = device.Bruno(user=self.params['user'],
+                              addr=self.params['addr'],
+                              bruno_prompt=self.params['bruno_prompt'],
+                              addr_ipv6=self.params['addr_ipv6'])
+    self.bruno.SetLogging(self.log)
+    if not self.p_ssh:
+      self.p_ssh = ssh.SSH(user=self.params['user'],
+                           addr=self.params['addr'],
+                           bruno_prompt=self.params['bruno_prompt'],
+                           addr_ipv6=self.params['addr_ipv6'])
+    self.bruno.dev_ssh = self.p_ssh
+    self.p_ssh.SetLogging(self.log)
+    return
+
+  def SshTunnel(self):
+    """Create ssh tunnel for the test."""
+    self.p_ssh.SshRetry(5, 15)
+    return
+
+  def VerifyDuplicatedAddress(self, ip_addr):
+    """Verify device can detect duplicated ipv6 address existing in network.
+
+    Set a duplicated ip address on the device.
+    Verify that the device can detect duplicated ip address
+
+    Args:
+      ip_addr: the duplicated address
+    Returns:
+      True: if duplicated detected
+      False: if duplicated address is not detected
+    """
+    time_stamp = self.bruno.GetTimeStamp()
+    self.bruno.dev_ssh.SendCmd('ip -6 addr add ' + ip_addr + '/64 dev br0')
+    time.sleep(1)
+    self.bruno.dev_ssh.SendCmd('dmesg | grep detected')
+    log_list = self.bruno.dev_ssh.GetCmdOutput()
+
+    pattern_line = ('\[\s*\d*\.\d{3}\]\s*\w*\d:\s* IPv6 duplicate address\s*('
+                    + ip_addr + ')\s*[/\d+]*\s*detected!')
+    self.bruno.dev_ssh.SendCmd('ip -6 addr del ' + ip_addr + '/64 dev br0')
+
+    if self.bruno.FindLineInLog(log_list, time_stamp, pattern_line):
+      info = self.log.CreateResultInfo('Pass', 'Duplicated IPv6 address '
+                                       'detected: ' + ip_addr)
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateResultInfo('Failed',
+                                       'Duplicated IPv6 address not detected: '
+                                       + ip_addr + '. Verification Failed!')
+      self.log.SendLine(self.test_info, info)
+    #[132344.324] br0: IPv6 duplicate address fe80:... detected!
+
+  def GetIPv4Address(self):
+    """Test Bruno IP address. testID: 11874104."""
+    # Use ifconfig to check interface IP address
+    self.p_ssh.SendCmd(r'ifconfig br0')
+    line = self.p_ssh.GetCmdOutput(3)[1]
+
+    # Get Bruno IP address
+    address = ip.IPADDR(line).IsLikeIpv4Address()
+    if address is not None:
+      ip.IPADDR(address).IsValidIpv4Address()
+      info = self.log.CreateProgressInfo('---',
+                                         'Verify IP: Bruno has IP address '
+                                         + address)
+      self.log.SendLine(self.test_info, info)
+      return address
+    else:
+      info = self.log.CreateErrorInfo('critical',
+                                      'Error! Bruno does not have IP address!'
+                                      ' Exit!')
+      self.log.SendLine(self.test_info, info)
+      return False
+
+    def GetNetMask(self):
+      """Get Netmask from device."""
+      self.p_ssh.SendCmd(r'ifconfig br0')
+      line = self.p_ssh.GetCmdOutput(3)[1]
+
+      netmask = re.search(r'mask [0-9]+(?:\.[0-9]+){3}', line).group()
+      address = ip.IPADDR(netmask).IsLikeIpv4Address()
+      if address is not None:
+        ip.IPADDR(address).IsValidIpv4Address()
+        info = self.log.CreateProgressInfo('---',
+                                           'Verify IP: Bruno has netmask: '
+                                           + address)
+        self.log.SendLine(self.test_info, info)
+        return address
+      else:
+        info = self.log.CreateErrorInfo('critical',
+                                        'Error! Bruno does not have netmask!'
+                                        ' Exit!')
+        self.log.SendLine(self.test_info, info)
+      return False
+
+  def VerifyIPv6(self):
+    """Test Bruno IPv6 address. testID: 11722376."""
+    # Use ip addr to check interface IPv6 address
+    self.p_ssh.SendCmd(r'ip addr show dev br0')
+
+    for line in reversed(self.p_ssh.p_ssh.before.splitlines()[-10:]):
+      ipv6_addr = re.search('inet6.*dynamic', line)
+      # Get Bruno IPv6 address
+      if ipv6_addr is not None:
+        info = self.log.CreateProgressInfo('---',
+                                           'Verify IPv6: '
+                                           'Bruno has IPv6 address: '
+                                           + ipv6_addr.group())
+        self.log.SendLine(self.test_info, info)
+        break
+    else:
+      info = self.log.CreateErrorInfo('intermediate',
+                                      'Wanring! '
+                                      'Bruno did not have IPv6 address')
+      self.log.SendLine(self.test_info, info)
+    return
+
+  def VerifyDHCPOverBrunoIS(self):
+    """Verify Bruno (connected to Bruno-IS) can get IPv4 address from RG.
+
+    test ID: 11721378
+    This test requires thinBruno is connected to FatBruno, then to RG.
+    """
+    ip_addr = self.GetIPv4Address()
+    if ip_addr:
+      info = self.log.CreateResultInfo('Pass', 'Bruno can get IP address from '
+                                       'RG while connecting to Bruno-IS. '
+                                       'IP address: '+ ip_addr)
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateResultInfo('Failed',
+                                       'Bruno cannot get IP address from '
+                                       'RG. Verification Failed!')
+      self.log.SendLine(self.test_info, info)
+
+  def Run(self):
+    """Run the test case."""
+    ####### Add your code here -- BEGIN #######
+    print 'Test Started...'
+
+    self.Init()
+    time.sleep(2)  # time delay between commands issued to Device
+    self.SshTunnel()
+
+    self.VerifyIPv6()
+
+    if self.test_info['testID'] == '11874111':
+      ip_addr = self.params['addr_ipv6_dup']
+      self.VerifyDuplicatedAddress(ip_addr)
+    elif self.test_info['testID'] == '11721378':
+      self.VerifyDHCPOverBrunoIS()
+
+    # TODO(lmeng): Test cases: '11724363', '11843145', '11723379', '11722378',
+    # '11792173', '11721382', '11843141', '11721379'
+
+    print 'Test Completed...'
+    ####### Add your code here -- END   #######
+
+  def Destructor(self):
+    """This is the destructor function to call for the user extened class."""
+    self.p_ssh.close()
+
+if __name__ == '__main__':
+  # Test cases defined in this file:
+  test_defined = ['11874111', '11724363', '11843145', '11723379', '11722378',
+                  '11721378', '11792173', '11721382', '11843141', '11721379']
+  # To execute test cases that defined in config file:
+  test_to_execute = testCase.TestCase.FindTestCaseToExecute('config.cfg')
+  for test_id in test_to_execute:
+    if test_id in test_defined:
+      test = BasicNetworkingTest(testID=test_id)
diff --git a/checkall b/checkall
new file mode 100755
index 0000000..bd5aa40
--- /dev/null
+++ b/checkall
@@ -0,0 +1,20 @@
+#!/bin/sh
+#
+# Display a one-line overview of whether each device is up.
+#
+mydir=$(dirname "$0")
+cd "$mydir"
+hosts=$(./config)
+
+out=
+./run $hosts -- 'ping -c1 -w1 -n $ip >/dev/null && echo yes || echo no' |
+sort | (
+  while read name ok; do
+    case "$ok" in
+      yes) out="$out${name%:}  " ;;
+      no)  out="$out^${name%:} " ;;
+      *)   ( echo; echo "Unexpected: '$name' '$ok'" ) >&2 ;;
+    esac
+  done
+  echo "$out"
+)
diff --git a/config.cfg b/config.cfg
index 6659a21..ea3fef7 100644
--- a/config.cfg
+++ b/config.cfg
@@ -1,39 +1,126 @@
-##############################################
-# This is the config file for the test cases #
-#                                            #
-# All test cases share the same config file. #
-# Each test case configuration starts with   #
-# the testID of that test case.              #
-#                                            #
-# comment line starts with '#' and will not  #
-# be parsed as configuration information     #
-##############################################
-testID: 11843140
-user = root
-addr = 192.168.1.4
-addr_ipv6 = 2605:a601:15:9001:21a:11ff:fe30:64d5
-pwd = google
-bruno_prompt = gfibertv#
-title =Image_Download
-expected_version_id=481109
-expected_version=bruno-jaguar-3
-downgrade_version=bruno-koala-2
-downgrade_version_id=662005
-ACS_URL = https://gfiber-acs-staging.appspot.com/cwmp
-# if authentication server is required to access the device
-jump_server = jmp.googlefiber.net
-# if athena server is required to access the device:
-athena_user = lmeng
-athena_pwd = changeme
+{
+      "Image_Download":{
+        "comment":["this is a comment line",
+                   "this configuration profile is for ImageDownload test case",
+                   "the acs_path is the google3 repository where upgrade_test.py locates. This test depends on it to modify ACS datastore."],
+        "testID": ["11843140", "14165036"],
+        "title" : {"11843140":"Image_Download", "14165036":"DownloadInterrupt"},
+        "user" : "root",
+        "addr" : "192.168.1.4",
+        "addr_ipv6" : "2605:a601:fe00:fd18:21a:11ff:fe30:64e6",
+        "pwd" : "google",
+        "bruno_prompt" : "gfibertv#",
+        "expected_version_id": "987010",
+        "expected_version": "bruno-monkey-4",
+        "downgrade_version": "bruno-monkey-3",
+        "downgrade_version_id": "1038003",
+        "acs_url" : "https://gfiber-acs-staging.appspot.com/cwmp",
+        "acs_path": "/home/lmeng/git_project/google3/isp/gfiber/testing/automated_tests/upgrade_test/",
+        "acs_app_id": "gfiber-acs-staging",
+        "acs_host_name": "gfiber-acs-staging.appspot.com",
+        "jump_server" : "jmp.googlefiber.net",
+        "athena_user" : "your_uname",
+        "athena_pwd" : "your_pwd",
+        "profile_id" : "557001"
+      },
 
+      "basic_network_testcases":{
+        "comment":["addr_ipv6_dup: the duplicated address to set on device, in",
+                   "or der to detect the address duplication error."],
+        "testID": ["11874111", "11721378"],
+        "title" : {"11874111":"DuplicateIPv6Addr", "11721378":"DHCPv4OverBruno-IS"},
+        "user" : "root",
+        "addr" : "192.168.1.4",
+        "addr_ipv6" : "2605:a601:fe00:fd18:21a:11ff:fe30:64e6",
+        "bruno_prompt" : "gfibertv#",
+        "expected_version_id": "987010",
+        "expected_version": "bruno-monkey-4",
+        "addr_ipv6_dup":"2605:a601:fe00:fd18:21a:11ff:fe30:6383"
+      },
+      
+      "dataModel":{
+        "testID": ["14187024", "14165033", "14315008", "14165035", "14314007",
+                   "11865133", "14335001", "14266498", "14319149", "14314008",
+                   "11722375", "14315009", "14314009", "14165038", "15643845"],
+        "title" : {"14187024":"TR140_DataModel",
+                   "14165033":"GVSB_DataModel",
+                   "14315008":"TR098_DataModel",
+                   "14165035":"MoCA_DataModel",
+                   "14314007":"TR181_DataModel",
+                   "11865133":"TR135_DataModel",
+                   "14335001":"X_CATAWAMPUS-ORG_DataModel",
+                   "14266498":"FlashDevice_DataModel",
+                   "14319149":"X_GOOGLE-COM_DataModel",
+                   "14314008":"TR157_DataModel",
+                   "11722375":"SetParameterValuesRPC",
+                   "14315009":"ManagementServerURL",
+                   "14314009":"MonitorTemperature",
+                   "14165038":"AutoChannelEnable",
+                   "15643845":"NTPTimeZoneConfig"},
+        "user" : "root",
+        "addr" : "192.168.1.4",
+        "addr_ipv6" : "2605:a601:fe00:fd18:21a:11ff:fe30:64e6",
+        "req_file" : "data/DataModel_requirement.cfg",
+        "bruno_prompt" : "gfibertv#",
+        "expected_version_id": "959004",
+        "expected_version": "bruno-monkey-2",
+        "acs_url" : "https://gfiber-acs-staging.appspot.com/cwmp",
+        "nbi_client_path" : "/home/lmeng/git_project/google3/blaze-bin/java/com/google/fiber/provisioning/acs/client/nbi/nbi_client",
+        "para_tree_file" : "ParameterTree.xml",
+        "from_which_param_to_set": "None",
+        "param_path" : "."
+      },
+      
+      "dataModel_WiFi":{
+        "comment":["wifi_params type: e.g., string_value, is the data type accepted by nbi_client RPC call."],
+        "testID": ["14165034"],
+        "title" : {"14165034":"WiFi_configuration"},
+        "user" : "root",
+        "addr" : "192.168.1.4",
+        "addr_ipv6" : "2605:a601:fe00:fd18:21a:11ff:fe30:64e6",
+        "bruno_prompt" : "gfibertv#",
+        "expected_version_id": "987010",
+        "expected_version": "bruno-monkey-4",
+        "acs_url" : "https://gfiber-acs-staging.appspot.com/cwmp",
+        "nbi_client_path" : "/home/lmeng/git_project/google3/blaze-bin/java/com/google/fiber/provisioning/acs/client/nbi/nbi_client",
+        "account_id":"5438577436",
+        "device_label":"0101209088BC",
+        "wifi_params":{
+            "Google.Wifi.SSID":{"string_value":"Home"},
+            "Google.Wifi.Home.SSID_2GHz":{"string_value":"Home"},
+            "Google.Wifi.Enable":{"boolean_value":"true"},
+            "Google.Wifi.BeaconType":{"string_value": "Basic"},
+            "Google.Wifi.BasicEncryptionModes":{"string_value":"WEPEncryption"}}
+      },
 
+      "basic_tr069_test_case":{
+        "comment":["Factory_reset test case needs to manually reset the device"],
+        "testID": ["11865129", "11721375"],
+        "title" : {"11865129":"IPv6_Connection",
+                   "11721375":"Factory_Reset"
+                  },
+        "user" : "root",
+        "addr" : {"value":"192.168.1.4", "comment":"device ipv4 address"},
+        "expected_version": "bruno-monkey-4",
+        "addr_ipv6" : {"value":"2605:a601:fe00:fd18:21a:11ff:fe30:6383",
+                       "comment":"device ipv6 address"},
+        "acs_ipv6" : "2001:4860:8005::8d",
+        "gvsb_host": "https://gvsb.e2e.gfsvc.com/rpc",
+        "acs_url" : "https://acs.e2e.gfsvc.com/cwmp",
+        "bruno_prompt" : "gfibertv#"
+      },
+      
+      "blocked_test_case":{
+        "testID": ["14314009", "14165038"],
+        "title" : {"14314009":"MonitorTemperature",
+                   "14165038":"AutoChannelEnable"},
+        "user" : "root",
+        "addr" : "192.168.1.4",
+        "addr_ipv6" : "2605:a601:fe00:fd18:21a:11ff:fe30:64e6",
+        "bruno_prompt" : "gfibertv#"
+      }
+}
 
-#testID: 14187024
-#title =DataModel
-#rq_file = 14187024-requirement.req
-#device_mURL = http://[2607:f8b0:400b:800::1011]/
-#device_IP = 2607:f8b0:400b:800::1011
-#serial_NO =
 
 
 
diff --git a/configs/core.py b/configs/core.py
index 1f751a1..f85e2fa 100644
--- a/configs/core.py
+++ b/configs/core.py
@@ -35,7 +35,7 @@
       if ival == False: ival = None
       if value == False: value = None
       if value == True and ival:
-        return True  # any nonempty value is true, so match it
+        continue  # any nonempty value is true, so match it
       if ival != value:
         return False
     return True
diff --git a/configs/core_test.py b/configs/core_test.py
index db97834..21b1f5f 100644
--- a/configs/core_test.py
+++ b/configs/core_test.py
@@ -14,20 +14,33 @@
 def testConfigCore():
   hosts = core.Hosts()
   WVPASSEQ(len(hosts), 0)
-  h = hosts.FindOrAdd(ether='11:22:33:44:55:66')
-  h.Set(name='testname', ip='1.2.3.4')
-  h.platform = 'bob'
+  h1 = hosts.FindOrAdd(ether='11:22:33:44:55:66')
+  h1.Set(name='testname', ip='1.2.3.4')
+  h1.platform = 'bob'
   WVPASSEQ(len(hosts), 1)
-  h2 = hosts.FindOrAdd(ip='1.2.3.4')
+  h1b = hosts.FindOrAdd(ip='1.2.3.4')
   WVPASSEQ(len(hosts), 1)
-  WVPASSEQ(h2.name, 'testname')
-  WVPASSEQ(h2.ether, '11:22:33:44:55:66')
-  WVPASSEQ(h, h2)
+  WVPASSEQ(h1b.name, 'testname')
+  WVPASSEQ(h1b.ether, '11:22:33:44:55:66')
+  WVPASSEQ(h1, h1b)
 
   # test Host.Query()
-  WVPASSEQ(hosts.Query(ip=True), [h])
+  WVPASSEQ(hosts.Query(ip=True), [h1])
   WVPASSEQ(hosts.Query(ip=False), [])
-  WVPASSEQ(hosts.Query(ip='1.2.3.4'), [h])
+  WVPASSEQ(hosts.Query(ip='1.2.3.4'), [h1])
   WVPASSEQ(hosts.Query(ip='1.2.3.5'), [])
   WVPASSEQ(hosts.Query(ip='1.2.3.4', maxcount=0), [])
   WVEXCEPT(core.QueryError, hosts.Query, ip='1.2.3.5', mincount=1)
+
+  # add a second host
+  h2 = hosts.FindOrAdd(ether='22:33:44:22:33:44')
+  h2.Set(name='test2')
+
+  WVPASSEQ(len(hosts), 2)
+  WVPASSEQ(hosts.Query(ip='1.2.3.4'), [h1])
+  WVPASSEQ(hosts.Query(ip=None), [h2])
+  WVPASSEQ(hosts.Query(name='test2'), [h2])
+  WVPASSEQ(hosts.Query(ip=True), [h1])
+  WVPASSEQ(sorted(hosts.Query(name=True)), sorted([h1, h2]))
+  WVPASSEQ(hosts.Query(ether=True, name='test2'), [h2])
+  WVPASSEQ(hosts.Query(ether=True, name='testname'), [h1])
diff --git a/data/DataModel_requirement.cfg b/data/DataModel_requirement.cfg
new file mode 100644
index 0000000..35f5a43
--- /dev/null
+++ b/data/DataModel_requirement.cfg
@@ -0,0 +1,296 @@
+Device.DeviceInfo.AdditionalHardwareVersion
+Device.DeviceInfo.AdditionalSoftwareVersion
+Device.DeviceInfo.Description
+Device.DeviceInfo.HardwareVersion
+Device.DeviceInfo.Manufacturer
+Device.DeviceInfo.ManufacturerOUI
+Device.DeviceInfo.MemoryStatus.Free
+Device.DeviceInfo.MemoryStatus.Total
+Device.DeviceInfo.ModelName
+Device.DeviceInfo.ProcessStatus.CPUUsage
+Device.DeviceInfo.ProcessStatus.Process.{i}.CPUTime
+Device.DeviceInfo.ProcessStatus.Process.{i}.Command
+Device.DeviceInfo.ProcessStatus.Process.{i}.PID
+Device.DeviceInfo.ProcessStatus.Process.{i}.Priority
+Device.DeviceInfo.ProcessStatus.Process.{i}.Size
+Device.DeviceInfo.ProcessStatus.Process.{i}.State
+Device.DeviceInfo.ProcessStatus.ProcessNumberOfEntries
+Device.DeviceInfo.ProcessorNumberOfEntries
+Device.DeviceInfo.ProductClass
+Device.DeviceInfo.SerialNumber
+Device.DeviceInfo.SoftwareVersion
+Device.DeviceInfo.SupportedDataModelNumberOfEntries
+Device.DeviceInfo.TemperatureStatus.TemperatureSensorNumberOfEntries
+Device.DeviceInfo.UpTime
+Device.DeviceInfo.VendorConfigFileNumberOfEntries
+Device.DeviceInfo.VendorLogFileNumberOfEntries
+Device.Ethernet.Interface.{i}.DuplexMode
+Device.Ethernet.Interface.{i}.Enable
+Device.Ethernet.Interface.{i}.LastChange
+Device.Ethernet.Interface.{i}.LowerLayers
+Device.Ethernet.Interface.{i}.MACAddress
+Device.Ethernet.Interface.{i}.MaxBitRate
+Device.Ethernet.Interface.{i}.Name
+Device.Ethernet.Interface.{i}.Stats.BroadcastPacketsReceived
+Device.Ethernet.Interface.{i}.Stats.BroadcastPacketsSent
+Device.Ethernet.Interface.{i}.Stats.BytesReceived
+Device.Ethernet.Interface.{i}.Stats.BytesSent
+Device.Ethernet.Interface.{i}.Stats.DiscardPacketsReceived
+Device.Ethernet.Interface.{i}.Stats.DiscardPacketsSent
+Device.Ethernet.Interface.{i}.Stats.ErrorsReceived
+Device.Ethernet.Interface.{i}.Stats.ErrorsSent
+Device.Ethernet.Interface.{i}.Stats.MulticastPacketsReceived
+Device.Ethernet.Interface.{i}.Stats.MulticastPacketsSent
+Device.Ethernet.Interface.{i}.Stats.PacketsReceived
+Device.Ethernet.Interface.{i}.Stats.PacketsSent
+Device.Ethernet.Interface.{i}.Stats.UnicastPacketsReceived
+Device.Ethernet.Interface.{i}.Stats.UnicastPacketsSent
+Device.Ethernet.Interface.{i}.Stats.UnknownProtoPacketsReceived
+Device.Ethernet.Interface.{i}.Status
+Device.Ethernet.Interface.{i}.Upstream
+Device.Ethernet.Interface.{i}.X_CATAWAMPUS-ORG_ActualBitRate
+Device.Ethernet.Interface.{i}.X_CATAWAMPUS-ORG_ActualDuplexMode
+Device.Ethernet.InterfaceNumberOfEntries
+Device.Ethernet.LinkNumberOfEntries
+Device.Ethernet.VLANTerminationNumberOfEntries
+Device.InterfaceStackNumberOfEntries
+Device.ManagementServer.CWMPRetryIntervalMultiplier
+Device.ManagementServer.CWMPRetryMinimumWaitInterval
+Device.ManagementServer.ConnectionRequestPassword
+Device.ManagementServer.ConnectionRequestURL
+Device.ManagementServer.ConnectionRequestUsername
+Device.ManagementServer.DefaultActiveNotificationThrottle
+Device.ManagementServer.EnableCWMP
+Device.ManagementServer.ManageableDeviceNumberOfEntries
+Device.ManagementServer.ParameterKey
+Device.ManagementServer.Password
+Device.ManagementServer.PeriodicInformEnable
+Device.ManagementServer.PeriodicInformInterval
+Device.ManagementServer.PeriodicInformTime
+Device.ManagementServer.STUNEnable
+Device.ManagementServer.URL
+Device.ManagementServer.UpgradesManaged
+Device.ManagementServer.Username
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.MACAddress
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.NodeID
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.PHYRxRate
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.PHYTxRate
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.PacketAggregationCapability
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.PreferredNC
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.QAM256Capable
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.RxBcastPowerLevel
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.RxErroredAndMissedPackets
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.RxPackets
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.RxPowerLevel
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.RxSNR
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.TxBcastRate
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.TxPackets
+Device.MoCA.Interface.{i}.AssociatedDevice.{i}.TxPowerControlReduction
+Device.MoCA.Interface.{i}.AssociatedDeviceNumberOfEntries
+Device.MoCA.Interface.{i}.BackupNC
+Device.MoCA.Interface.{i}.CurrentOperFreq
+Device.MoCA.Interface.{i}.CurrentVersion
+Device.MoCA.Interface.{i}.Enable
+Device.MoCA.Interface.{i}.FirmwareVersion
+Device.MoCA.Interface.{i}.HighestVersion
+Device.MoCA.Interface.{i}.LastChange
+Device.MoCA.Interface.{i}.LastOperFreq
+Device.MoCA.Interface.{i}.LowerLayers
+Device.MoCA.Interface.{i}.MACAddress
+Device.MoCA.Interface.{i}.Name
+Device.MoCA.Interface.{i}.NetworkCoordinator
+Device.MoCA.Interface.{i}.NodeID
+Device.MoCA.Interface.{i}.PacketAggregationCapability
+Device.MoCA.Interface.{i}.PrivacyEnabled
+Device.MoCA.Interface.{i}.QAM256Capable
+Device.MoCA.Interface.{i}.Stats.BroadcastPacketsReceived
+Device.MoCA.Interface.{i}.Stats.BroadcastPacketsSent
+Device.MoCA.Interface.{i}.Stats.BytesReceived
+Device.MoCA.Interface.{i}.Stats.BytesSent
+Device.MoCA.Interface.{i}.Stats.DiscardPacketsReceived
+Device.MoCA.Interface.{i}.Stats.DiscardPacketsSent
+Device.MoCA.Interface.{i}.Stats.ErrorsReceived
+Device.MoCA.Interface.{i}.Stats.ErrorsSent
+Device.MoCA.Interface.{i}.Stats.MulticastPacketsReceived
+Device.MoCA.Interface.{i}.Stats.MulticastPacketsSent
+Device.MoCA.Interface.{i}.Stats.PacketsReceived
+Device.MoCA.Interface.{i}.Stats.PacketsSent
+Device.MoCA.Interface.{i}.Stats.UnicastPacketsReceived
+Device.MoCA.Interface.{i}.Stats.UnicastPacketsSent
+Device.MoCA.Interface.{i}.Stats.UnknownProtoPacketsReceived
+Device.MoCA.Interface.{i}.Status
+Device.MoCA.Interface.{i}.Upstream
+Device.MoCA.InterfaceNumberOfEntries
+Device.PeriodicStatistics.MaxReportSamples
+Device.PeriodicStatistics.MinSampleInterval
+Device.PeriodicStatistics.SampleSet.{i}.Enable
+Device.PeriodicStatistics.SampleSet.{i}.FetchSamples
+Device.PeriodicStatistics.SampleSet.{i}.Name
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.CalculationMode
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Enable
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Failures
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.HighThreshold
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.LowThreshold
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Reference
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SampleMode
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SampleSeconds
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SuspectData
+Device.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Values
+Device.PeriodicStatistics.SampleSet.{i}.ParameterNumberOfEntries
+Device.PeriodicStatistics.SampleSet.{i}.ReportEndTime
+Device.PeriodicStatistics.SampleSet.{i}.ReportSamples
+Device.PeriodicStatistics.SampleSet.{i}.ReportStartTime
+Device.PeriodicStatistics.SampleSet.{i}.SampleInterval
+Device.PeriodicStatistics.SampleSet.{i}.SampleSeconds
+Device.PeriodicStatistics.SampleSet.{i}.Status
+Device.PeriodicStatistics.SampleSet.{i}.TimeReference
+Device.PeriodicStatistics.SampleSetNumberOfEntries
+Device.Services.STBService.{i}.Components.FrontEnd.{i}.IP.IGMP.ClientGroup.{i}.GroupAddress
+Device.Services.STBService.{i}.Components.FrontEnd.{i}.IP.IGMP.ClientGroupNumberOfEntries
+Device.Services.STBService.{i}.Components.FrontEndNumberOfEntries
+Device.Services.STBServiceNumberOfEntries
+Device.Services.StorageServices.Capabilities.FTPCapable
+Device.Services.StorageServices.Capabilities.HTTPCapable
+Device.Services.StorageServices.Capabilities.HTTPSCapable
+Device.Services.StorageServices.Capabilities.HTTPWritable
+Device.Services.StorageServices.Capabilities.SFTPCapable
+Device.Services.StorageServices.Capabilities.SupportedFileSystemTypes
+Device.Services.StorageServices.Capabilities.SupportedNetworkProtocols
+Device.Services.StorageServices.Capabilities.SupportedRaidTypes
+Device.Services.StorageServices.Capabilities.VolumeEncryptionCapable
+Device.Services.StorageServices.Enable
+Device.Services.StorageServices.LogicalVolume.{i}.Capacity
+Device.Services.StorageServices.LogicalVolume.{i}.Enable
+Device.Services.StorageServices.LogicalVolume.{i}.FileSystem
+Device.Services.StorageServices.LogicalVolume.{i}.FolderNumberOfEntries
+Device.Services.StorageServices.LogicalVolume.{i}.Name
+Device.Services.StorageServices.LogicalVolume.{i}.Status
+Device.Services.StorageServices.LogicalVolume.{i}.ThresholdLimit
+Device.Services.StorageServices.LogicalVolume.{i}.UsedSpace
+Device.Services.StorageServices.LogicalVolume.{i}.X_CATAWAMPUS-ORG_ReadOnly
+Device.Services.StorageServices.LogicalVolumeNumberOfEntries
+Device.Services.StorageServices.PhysicalMedium.{i}.Capacity
+Device.Services.StorageServices.PhysicalMedium.{i}.ConnectionType
+Device.Services.StorageServices.PhysicalMedium.{i}.FirmwareVersion
+Device.Services.StorageServices.PhysicalMedium.{i}.Health
+Device.Services.StorageServices.PhysicalMedium.{i}.HotSwappable
+Device.Services.StorageServices.PhysicalMedium.{i}.Model
+Device.Services.StorageServices.PhysicalMedium.{i}.Name
+Device.Services.StorageServices.PhysicalMedium.{i}.Removable
+Device.Services.StorageServices.PhysicalMedium.{i}.SMARTCapable
+Device.Services.StorageServices.PhysicalMedium.{i}.SerialNumber
+Device.Services.StorageServices.PhysicalMedium.{i}.Vendor
+Device.Services.StorageServices.PhysicalMediumNumberOfEntries
+Device.Services.StorageServices.StorageArrayNumberOfEntries
+Device.Services.StorageServices.UserAccountNumberOfEntries
+Device.Services.StorageServices.UserGroupNumberOfEntries
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.BadEraseBlocks
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.CorrectedErrors
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.EraseBlockSize
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.IOSize
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.MaxEraseCount
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.Name
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.ReservedEraseBlocks
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.SubVolume.{i}.DataBytes
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.SubVolume.{i}.Name
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.SubVolume.{i}.Status
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.SubVolumeNumberOfEntries
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.TotalEraseBlocks
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMedia.{i}.UncorrectedErrors
+Device.Services.StorageServices.X_CATAWAMPUS-ORG_FlashMediaNumberOfEntries
+InternetGatewayDevice.DeviceInfo.AdditionalHardwareVersion
+InternetGatewayDevice.DeviceInfo.AdditionalSoftwareVersion
+InternetGatewayDevice.DeviceInfo.Description
+InternetGatewayDevice.DeviceInfo.HardwareVersion
+InternetGatewayDevice.DeviceInfo.Manufacturer
+InternetGatewayDevice.DeviceInfo.ManufacturerOUI
+InternetGatewayDevice.DeviceInfo.ModelName
+InternetGatewayDevice.DeviceInfo.ModemFirmwareVersion
+InternetGatewayDevice.DeviceInfo.ProductClass
+InternetGatewayDevice.DeviceInfo.SerialNumber
+InternetGatewayDevice.DeviceInfo.SoftwareVersion
+InternetGatewayDevice.DeviceInfo.SpecVersion
+InternetGatewayDevice.DeviceInfo.UpTime
+InternetGatewayDevice.DeviceInfo.VendorConfigFileNumberOfEntries
+InternetGatewayDevice.LANDevice.{i}.LANEthernetInterfaceNumberOfEntries
+InternetGatewayDevice.LANDevice.{i}.LANUSBInterfaceNumberOfEntries
+InternetGatewayDevice.LANDevice.{i}.LANWLANConfigurationNumberOfEntries
+InternetGatewayDevice.LANDeviceNumberOfEntries
+InternetGatewayDevice.ManagementServer.ConnectionRequestPassword
+InternetGatewayDevice.ManagementServer.ConnectionRequestURL
+InternetGatewayDevice.ManagementServer.ConnectionRequestUsername
+InternetGatewayDevice.ManagementServer.DefaultActiveNotificationThrottle
+InternetGatewayDevice.ManagementServer.EnableCWMP
+InternetGatewayDevice.ManagementServer.ParameterKey
+InternetGatewayDevice.ManagementServer.Password
+InternetGatewayDevice.ManagementServer.PeriodicInformEnable
+InternetGatewayDevice.ManagementServer.PeriodicInformInterval
+InternetGatewayDevice.ManagementServer.PeriodicInformTime
+InternetGatewayDevice.ManagementServer.URL
+InternetGatewayDevice.ManagementServer.UpgradesManaged
+InternetGatewayDevice.ManagementServer.Username
+InternetGatewayDevice.PeriodicStatistics.MaxReportSamples
+InternetGatewayDevice.PeriodicStatistics.MinSampleInterval
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Enable
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.FetchSamples
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Name
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.CalculationMode
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Enable
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Failures
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.HighThreshold
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.LowThreshold
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Reference
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SampleMode
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SampleSeconds
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.SuspectData
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Parameter.{i}.Values
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.ParameterNumberOfEntries
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.ReportEndTime
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.ReportSamples
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.ReportStartTime
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.SampleInterval
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.SampleSeconds
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.Status
+InternetGatewayDevice.PeriodicStatistics.SampleSet.{i}.TimeReference
+InternetGatewayDevice.PeriodicStatistics.SampleSetNumberOfEntries
+InternetGatewayDevice.Time.CurrentLocalTime
+InternetGatewayDevice.Time.Enable
+InternetGatewayDevice.Time.LocalTimeZoneName
+InternetGatewayDevice.WANDeviceNumberOfEntries
+TraceRoute.DSCP
+TraceRoute.DataBlockSize
+TraceRoute.DiagnosticsState
+TraceRoute.Host
+TraceRoute.Interface
+TraceRoute.MaxHopCount
+TraceRoute.NumberOfTries
+TraceRoute.ResponseTime
+TraceRoute.RouteHopsNumberOfEntries
+TraceRoute.Timeout
+X_CATAWAMPUS-ORG_CATAWAMPUS.RuntimeEnvInfo
+X_GOOGLE-COM_GFIBERTV.Mailbox.Name
+X_GOOGLE-COM_GFIBERTV.Mailbox.Node
+X_GOOGLE-COM_GFIBERTV.Mailbox.NodeList
+X_GOOGLE-COM_GFIBERTV.Mailbox.Value
+X_GOOGLE-COM_GVSB.EpgPrimary
+X_GOOGLE-COM_GVSB.EpgSecondary
+X_GOOGLE-COM_GVSB.GvsbChannelLineup
+X_GOOGLE-COM_GVSB.GvsbKick
+X_GOOGLE-COM_GVSB.GvsbServer
+STBService.{i}.ServiceMonitoring.MainStream.{i}.Total.MPEG2TSStats.TSPacketsReceived
+STBService.{i}.ServiceMonitoring.MainStream.{i}.Total.MPEG2TSStats.PacketDiscontinuityCounter
+STBService.{i}.ServiceMonitoring.MainStream.{i}.Total.DejitteringStats.EmptyBufferTime
+STBService.{i}.ServiceMonitoring.MainStream.{i}.Total.DejitteringStats.Overruns
+STBService.{i}.ServiceMonitoring.MainStream.{i}.Total.DejitteringStats.Underruns
+STBService.{i}.ServiceMonitoring.MainStream.{i}.Total.TCPStats.PacketsReceived
+STBService.{i}.ServiceMonitoring.MainStream.{i}.Total.TCPStats.PacketsRetransmitted
+STBService.{i}.ServiceMonitoring.MainStream.{i}.Total.TCPStats.BytesReceived
+STBService.{i}.Components.HDMI.{i}.DisplayDevice.Status
+STBService.{i}.Components.HDMI.{i}.DisplayDevice.SupportedResolutions
+STBService.{i}.Components.HDMI.{i}.DisplayDevice.PreferredResolution
+STBService.{i}.Components.HDMI.{i}.ResolutionValue
+STBService.{i}.Components.HDMI.{i}.DisplayDevice.EEDID
+STBService.{i}.Components.HDMI.{i}.DisplayDevice.AutoLipSyncSupport
+STBService.{i}.Components.HDMI.{i}.DisplayDevice.VideoLatency
+STBService.{i}.Components.HDMI.{i}.DisplayDevice.HDMI3DPresent
+STBService.{i}.Components.HDMI.{i}.DisplayDevice.CECSupport
diff --git a/dataModel.py b/dataModel.py
new file mode 100755
index 0000000..af45a0b
--- /dev/null
+++ b/dataModel.py
@@ -0,0 +1,1430 @@
+#!/usr/bin/python
+# Copyright 2012 Google Inc. All Rights Reserved.
+
+"""This test performs TR069 related DataModel verification.
+
+This class extends the TestCase class, it does the following:
+  1. Configure/Verify Bruno with correct TR69 parameters:
+       - ACS URL
+       - Image Version
+  2. Verify Bruno has connection to ACS (IPv6/IPv4)
+  3. Set/Get TR69 data model parameters:
+       -  getParameterNames()
+       -  getParameterValues()
+  4. Data mode:
+       - Data Models for GVSB
+       - Data Models for TR098
+       - Data Models for MoCA
+       - Data Models for TR140
+       - Data Models for TR181
+  5. This file includes the following test cases:
+       (all data models test case are pending on bug: http://b/issue?id=6813492
+        SetParameterValues does not work currently)
+       - testID: 14165033  7.6.13 Support of Data Models for GVSB (TBD)
+       - testID: 14315008  7.6.14 Support of Data Models for TR098
+       - testID: 14165035  7.6.15 Support of Data Models for MoCA
+       - testID: 14187024  7.6.16 Support of Data Models for TR140
+       - testID: 14314007  7.6.17 Support of Data Models for TR181
+       - testID: 11865133  7.6.9.1 Support of Data Models: TR135
+       - testID: 14335001  7.6.31 Implement X_CATAWAMPUS-ORG parameters
+       - testID: 14319149  7.6.29 Implement X_GOOGLE-COM data model for Sage
+                                  information
+       - testID: 14314008  7.6.30 Implement tr-157 PeriodicStatistics
+
+       - testID: 14315009  7.6.20 Support of modification of
+                                  InternetGatewayDevice.ManagementServer.URL
+       - testID: 14314009  7.6.35 Monitor temperature
+       - testID: 14266498  7.6.36 X_CATAWAMPUS-ORG_FlashDevice Storage data
+                                  model (for tracking flash wear)
+       - testID: 14165038  7.6.37 Implement AutoChannelEnable in catawampus
+       - testID: 11722375  7.6.6  TR069 - SetParameter RPC Request
+       - testID: 15643845  7.6.39 NTP-Time zone configuration
+       - testID: 14165034  7.6.14.1 WiFi Test: ACS configuration
+"""
+
+__author__ = 'Lehan Meng (lmeng@google.com)'
+
+
+import os
+import re
+import string
+import time
+import xml.etree.ElementTree as ET
+
+import acs
+import device
+import ssh
+import testCase
+
+
+class DataModelTest(testCase.TestCase):
+  """Class for TR69 DataModel and parameter verification test.
+
+  Configuration Parameters:
+    TBD
+    testID: 14187024
+    title =DataModel
+    req_file = 14187024-requirement.req
+  """
+
+  def Init(self):
+    """Initialte the test case."""
+    info = self.log.CreateResultInfo(
+        '---', 'Data model verification test started...')
+    self.log.SendLine(self.test_info, info)
+    self.bruno = device.Bruno(user=self.params['user'],
+                              addr=self.params['addr'],
+                              pwd=self.params['pwd'],
+                              cmd_prompt=self.params['bruno_prompt'])
+    if not self.p_ssh:
+      self.p_ssh = ssh.SSH(user=self.params['user'],
+                           addr=self.params['addr'],
+                           pwd=self.params['pwd'],
+                           bruno_prompt=self.params['bruno_prompt'],
+                           addr_ipv6=self.params['addr_ipv6'])
+    self.p_ssh.SetLogging(self.log)
+    self.bruno.SetLogging(self.log)
+    self.bruno.dev_ssh = self.p_ssh
+
+    self.__short_delay = 30  #short delay: 5 seconds
+    self.__delay = 80  #medium delay: 180 seconds
+    self.__long_delay = 120  #long delay: 600 seconds
+
+    self.req_file = open(self.params['req_file'], 'r')
+    self.req_tr069_params = []
+    self.acs_instance = acs.ACS()
+    self.acs_instance.SetLogging(self.log)
+
+  def SetACSURL(self, url=None):
+    """Set the ACS URL on device."""
+    if not url:
+      url = self.params['acs_url']
+    self.p_ssh.SendCmd('echo '+ url + ' > /tmp/cwmp/acs_url')
+    self.p_ssh.GetCmdOutput()[1].rstrip()
+
+  def GetACSURL(self):
+    """Get ACS URL from Device."""
+    self.p_ssh.SendCmd(r'cat /tmp/cwmp/acs_url')
+    line = self.p_ssh.GetCmdOutput()[1].strip()
+    m = re.match('http[s]?://([\w-]+\.)+\.com/cwmp', line)
+    if m is None:
+      info = self.log.CreateErrorInfo('Warning',
+                                      'Can not get ACS url')
+      self.log.SendLine(None, info)
+    else:
+      info = self.log.CreateProgressInfo('---',
+                                         'Get ACS URL from device file: '
+                                         + line)
+      self.log.SendLine(None, info)
+    return line
+
+  def GetCurrentVersion(self):
+    """Get current software version."""
+    self.p_ssh.SendCmd('more /etc/version')
+    return self.p_ssh.GetCmdOutput()[1].strip()
+
+  def TestEnvCheck(self):
+    """Verify device configuration meets the test requirement."""
+    acs_url = self.GetACSURL()
+    if acs_url != self.params['acs_url']:
+      info = self.log.CreateErrorInfo(
+          'Warning', 'ACS URL incorrect!. Expected: '
+          + self.params['acs_url'] + ', Current: '
+          + acs_url)
+      self.log.SendLine(self.test_info, info)
+      #os.sys.exit(1)
+      info = self.log.CreateProgressInfo(
+          '---', 'Reset ACS URL to Expected: ' + self.params['acs_url'])
+      self.log.SendLine(self.test_info, info)
+      self.SetACSURL()
+
+    current_version = self.GetCurrentVersion()
+    if (not current_version
+        or current_version != self.params['expected_version']):
+      info = self.log.CreateErrorInfo(
+          'critical', 'Not expected image version. Expected: '
+          + self.params['expected_version'] + ', Current: '
+          + current_version)
+      self.log.SendLine(self.test_info, info)
+    else:
+      self.AddConfigParams('current_version', current_version)
+
+  def CheckRequirement(self, chk_list, req_list):
+    """Check parameter names with parameters in requirement file.
+
+    This file will compare the parameters from Device and from requirement
+    File. The differences between the two will be written to the result log
+    file
+    Args:
+      chk_list: the list of parameters results returned by query
+      req_list: the list of parameters from requirement
+    Returns:
+      missing_list: the list of parameters that is missing from requirement
+      net_list: the list of parameters that are newly added but not in the
+                requirement
+    """
+    missing_list = []
+    new_list = []
+    #check missing parameters from Requirement
+    for req_line in req_list:
+      verify = False
+      for chk_line in chk_list:
+        if req_line == chk_line:
+          verify = True
+        elif string.find(chk_line, req_line) == 0:
+          verify = True
+      if not verify:
+        missing_list.append(req_line)
+
+    #check newly added parameters to Requirement
+    for line in chk_list:
+      if req_list.count(line) < 1 and new_list.count(line) < 1:
+        new_list.append(line)
+    return (missing_list, new_list)
+
+  def GetACSAppFromURL(self):
+    """Parse ACS appID from ACS URL string."""
+    return re.sub(r'https?://(.*).appspot.com/cwmp', r'\1',
+                  self.params['acs_url'])
+
+  def GetSerialNumber(self):
+    """This function returns Bruno device serial number."""
+    self.p_ssh.SendCmd('hnvram -r 1st_serial_number')
+    line = self.p_ssh.GetCmdOutput()[1].strip()
+    if line is None:
+      info = self.log.CreateErrorInfo('Warning',
+                                      'Can not get serial number')
+      self.log.SendLine(None, info)
+      return False
+    return re.sub(r'1st_serial_number=([0-9A-Z]{12})', r'\1', line)
+
+  def GetProductClass(self):
+    """Get product class from device system."""
+    self.p_ssh.SendCmd('cat /etc/platform')
+    return self.p_ssh.GetCmdOutput()[1].strip()
+
+  def BatchSetParametersValues(self, tree, param):
+    """Set multiple parameter values in a single nbi_client RPC call.
+
+    Set multiple parameter values
+    Args:
+      tree: the parameter names are in TreeElement structure. It has
+            parameter 'writable' states and test_value to set.
+            Parameter names/paths are sorted in alphabetical order in tree.
+      param: the parameters name list that needs to be set with new value
+    """
+    nbi_path = self.params['nbi_client_path']
+    app_id = self.GetACSAppFromURL()
+    product_class = self.GetProductClass()
+    serial_no = self.GetSerialNumber()
+
+    container = tree.findall('parameter_value_struct')
+    batch_size = 1
+    counter = 0
+    parameter_struct = ''
+
+    for node in container:
+      name = node.findtext('name')
+
+      if name in param:
+        nbi_type = node.findtext('nbi_type')
+
+        if not nbi_type:
+          info = self.log.CreateErrorInfo('Warning',
+                                          'Can not determin parameter type!')
+          self.log.SendLine(None, info)
+
+        test_value = node.findtext('test_value')
+        if nbi_type == 'string_value':
+          test_value = '"' + test_value + '"'
+        if (node.findtext('writable') == '1'
+            or node.findtext('writable') == 'true'):
+          parameter_struct += ('parameter_value_struct: <name: "'
+                               + name + '" ' + nbi_type + ': '
+                               + test_value + '> ')
+          counter += 1
+        else:
+          continue
+
+        if counter >= batch_size:
+          counter = 0
+          cmd = (nbi_path
+                 + ' --server /gns/project/apphosting/stubby/prod-appengine/'
+                 '*/' + app_id + '/default/* --service CpeParametersService '
+                 '--method SetParameterValues --request \''
+                 'cpe_id: <oui: "F88FCA" product_class: "' + product_class
+                 + '" serial_no: "' + serial_no
+                 + '"> request: <parameter_list < '
+                 + parameter_struct + ' > parameter_key: "myParamKey">\'')
+          print cmd
+          parameter_struct = ''
+          time_stamp = self.GetTimeStamp()
+          call_result = self.acs_instance.StubbyClientCall(cmd)
+          print call_result
+          if not self.acs_instance.CheckCmdCall(call_result):
+            # command not succeed according to nbi_client reply
+            # However, there is possibility that the request is successful,
+            # nbi_client reports a failure only because the timeout 60s.
+            # check device log to see if this request actually fails
+            self.p_ssh.SendCmd('dmesg | grep cwmpd:')
+            # read the last 500 lines from log
+            log_list = self.p_ssh.GetCmdOutput(500)
+            log_list.reverse()
+            reply_msg = self.ParseDeviceLogForError(
+                time_stamp, log_list, name, 'SetParameterValues')
+            if '<soap:Fault>' in reply_msg[0] or '<soap:fault>' in reply_msg[0]:
+              # failed to set parameter values (on nbi_client and device):
+              info = self.log.CreateErrorInfo(
+                  'Critical', 'Batch SetParameterValues() failed on Parameter: '
+                  + name + '. Parameter type: ' + nbi_type
+                  + '. Set test value: ' + test_value + '. Error message: \n'
+                  + ''.join(reply_msg))
+              self.log.SendLines(self.test_info, info)
+            elif '<cwmp:SetParameterValuesResponse>' in reply_msg[0]:
+              # nbi_reports failure, but success on device
+              info = self.log.CreateResultInfo(
+                  '---', 'Batch SetParameterValues() Succeeded on Parameter: '
+                  + name + '. Parameter type: ' + nbi_type
+                  + '. Set test value: ' + test_value)
+              self.log.SendLine(self.test_info, info)
+            else:
+              info = self.log.CreateErrorInfo(
+                  'Critical', 'Batch SetParameterValues() failed on Parameter: '
+                  + name + '. Parameter type: ' + nbi_type
+                  + '. Set test value: ' + test_value + '. Error message: \n'
+                  + ''.join(reply_msg))
+              self.log.SendLines(self.test_info, info)
+          else:
+            # Succeed, logging to result file:
+            self.log.SendTestData(call_result)
+            info = self.log.CreateResultInfo(
+                '---', 'Batch SetParameterValues() Succeeded on Parameter: '
+                + name + '. Parameter type: ' + nbi_type
+                + '. Set test value: ' + test_value)
+            self.log.SendLine(self.test_info, info)
+
+  def SetParameterValues(self, param='', param_type='', value=''):
+    """Set parameter values on Device.
+
+    Set parameter values
+    Args:
+      param: the parameter name that needs to be set with new value
+      param_type: type of value: string, boolean, long_int, etc
+      value: parameter value
+    Returns:
+      return setParameterValues if succeeded.
+      return error information if failed
+    """
+    nbi_path = self.params['nbi_client_path']
+    app_id = self.GetACSAppFromURL()
+    product_class = self.GetProductClass()
+    serial_no = self.GetSerialNumber()
+    # parse type:
+    if 'string' in param_type:
+      param_type = 'string_value'
+      value = '"' + value + '"'
+    elif 'bool' in param_type:
+      param_type = 'boolean_value'
+    elif 'unsignedInt' in param_type:
+      param_type = 'long_value'
+    else:
+      info = self.log.CreateErrorInfo(
+          'Critical', 'Unknown parameter type: ' + param_type)
+      self.log.SendLines(self.test_info, info)
+      os.sys.exit(1)
+
+    cmd = (nbi_path + ' --server /gns/project/apphosting/stubby/prod-appengine/'
+           '*/' + app_id + '/default/* --service CpeParametersService '
+           '--method SetParameterValues --request \''
+           'cpe_id: <oui: "F88FCA" product_class: "' + product_class
+           + '" serial_no: "' + serial_no + '"> request: <parameter_list < '
+           'parameter_value_struct: <name: "' + param +
+           '" ' + param_type + ': ' + value
+           + '> > parameter_key: "myParamKey">\'')
+    print cmd
+    call_result = self.acs_instance.StubbyClientCall(cmd)
+    if 'FAILED' in call_result[0]:
+      info = self.log.CreateErrorInfo(
+          'Warning', 'ACS failed on SetParameterValues query: ' + cmd)
+      self.log.SendLines(self.test_info, info)
+      return False
+    else:
+      print call_result
+    return call_result
+
+  def GetParameterNames(self, param='.', next_level=True):
+    """Get next level parameter names of the current queried parameter.
+
+    The next level parameter names will be output to result file
+    Due to the large number of parameter instances, parameters in the same
+    parameter family will be grouped together into a single nbi_client
+    RPC call (getParameterNames() call, this call will feedback the access
+    state information). In order to minimize the total processing time, as
+    well as the number of RPC calls to ACS appEngine. This will greatly
+    reduce the execution time.
+    Each getParameterNames call will be created as an individual thread.
+    And multiple calls are made in parallel.
+
+    Args:
+      param:
+        This is the parameter that is queried for its next level names
+        Note that currently GetParameterNames does not support multiple
+        parameter names in a query command. If parameter name is a dot,
+        and the next level is 'False', then device will return all
+        parameters under the queried parameter path
+      next_level:
+        if this parameter is True:
+          only the next level parameter names are returned
+        if this parameter is False:
+          all parameter in the requested parameter path will be returned
+    Returns:
+      return the nbi_client RPC call response. Response can be used to
+      parse for parameters' name, access attribute, etc.
+    """
+    nbi_path = self.params['nbi_client_path']
+    app_id = self.GetACSAppFromURL()
+    product_class = self.GetProductClass()
+    serial_no = self.GetSerialNumber()
+    if next_level:
+      next_level = 'true'
+    else:
+      next_level = 'false'
+    cmd = (nbi_path + ' --server /gns/project/apphosting/stubby/prod-appengine/'
+           '*/' + app_id + '/default/* --service CpeParametersService '
+           '--method GetParameterNames --request \''
+           'cpe_id: <oui: "F88FCA" product_class: "' + product_class
+           + '" serial_no: "' + serial_no + '"> request: <parameter_path: "'
+           + param + '" next_level: ' + next_level + '>\'')
+    print cmd
+    call_result = self.acs_instance.StubbyClientCall(cmd)
+    if 'FAILED' in call_result[0]:
+      self.nbi_result = []
+      info = self.log.CreateErrorInfo(
+          'Warning', 'ACS failed on GetParameterNames query: ' + cmd)
+      self.log.SendLines(self.test_info, info)
+      return False
+    else:
+      self.nbi_result = call_result
+    print call_result
+    return call_result
+
+  def GetParameterValues(self, param=''):
+    """Make a Get parameter value(s) query at ACS to request the device.
+
+    This method get value(s) of the designated parameter(s)
+    Args:
+      param: this is the parameter (path) that need to get value(s) from.
+             1. If this path ends with a dot '.', all parameters
+                under this path will be queried for values.
+             2. If this path does not end with a '.', the parameter
+                must be a leaf parameter which has a value.
+    Returns:
+      This method returns a list of parameter values and record them to file.
+    """
+    nbi_path = self.params['nbi_client_path']
+    if param == '.':
+      param = ''
+    app_id = self.GetACSAppFromURL()
+    product_class = self.GetProductClass()
+    serial_no = self.GetSerialNumber()
+    cmd = (nbi_path + ' --server /gns/project/apphosting/stubby/prod-appengine/'
+           '*/' + app_id + '/default/* --service CpeParametersService '
+           '--method GetParameterValues --request \''
+           'cpe_id: <oui: "F88FCA" product_class: "' + product_class
+           + '" serial_no: "' + serial_no + '"> request: <parameter_names: "'
+           +param + '">\'')
+
+    print cmd
+    call_result = self.acs_instance.StubbyClientCall(cmd)
+    if 'FAILED' in call_result[0]:
+      self.nbi_result = []
+      info = self.log.CreateErrorInfo(
+          'Warning', 'ACS failed on GetParameterValues query: ' + cmd)
+      self.log.SendLines(self.test_info, info)
+      return False
+    else:
+      self.nbi_result = call_result
+    print call_result
+    return call_result
+
+  def FindEndofRequest(self, log_list, index, pattern):
+    """Find the end of a request string from device log.
+
+    Some times the close tag, for example: </cwmp:SetParameterValues> is
+    separated to two consecutive log lines, due to the line length limitation.
+    This method try to search the request closing tag on separate lines
+
+    Args:
+      log_list: the device log list.
+      index: the index of the line to search for tag
+      pattern: the closing tag that need to be searched in log file
+    Returns:
+      True: if pattern find in log
+      False: if pattern not find in log
+    """
+    if index >= len(log_list)-1:
+      if pattern in log_list[index]:
+        return True
+      else:
+        return False
+
+    s = re.sub(r'\[\s*\d*\.\d*\]\s*cwmpd:\s*(.*)', r'\1', log_list[index + 1])
+    s = log_list[index].strip() + s.strip()
+
+    if pattern in s:
+      return True
+    return False
+
+  def MatchParameterName(self, line, param_name, rpc):
+    """Match rpc request logs to a parameter name."""
+    if rpc == 'GetParameterNames':
+      m = re.search(r'.*<ParameterPath>(.*)</ParameterPath>.*', line)
+      if m is not None and param_name in m.group(1):
+        return True
+      else:
+        return False
+    elif rpc == 'GetParameterValues':
+      m = re.search(r'.*<cwmp:GetParameterValues>.*<ParameterNames.*>(.*)'
+                    '</ParameterNames></cwmp:GetParameterValues>.*', line)
+      if m is not None and param_name in m.group(1):
+        return True
+      else:
+        return False
+    elif rpc == 'SetParameterValues':
+      m = re.search(r'.*<Name>(.*)</Name><Value.*', line)
+      if m is not None and param_name in m.group(1):
+        return True
+      else:
+        return False
+    else:
+      return False
+
+  def ParseDeviceLogForError(self, time_stamp, log_list, param, rpc):
+    """Parse the log information from device, Looking for cwmp errors.
+
+    Args:
+      time_stamp: looking for log events that happened only after this time
+      log_list: the cwmp event information obtained from device log
+      param: the parameter name to get value from
+      rpc: the rpc function name to search in log
+    Returns:
+      return cwmp error message found in device log.
+    """
+
+    reply_msg = []
+    start_of_rpc_reply = False
+    end_of_rpc_reply = False
+    start_of_rpc_request = False
+    end_of_rpc_request = False
+    param_matching = False
+    current_index = 0
+
+    for line in log_list:
+      # check only the getParameter query starts after the specified time:
+      if float(self.GetTimeStamp(line)) > float(time_stamp):
+        if 'CPE RECEIVED (at' in line and not start_of_rpc_request:
+          index = log_list.index(line)
+          # macthing the request query in log messages
+          for i in range(index+1, len(log_list)-1):
+            if i < len(log_list):
+              # match rpc request string
+              if '<cwmp:' + rpc + '>' in log_list[i]:
+                start_of_rpc_request = True
+              # match rpc request string end
+              if (self.FindEndofRequest(log_list, i, '</cwmp:' + rpc + '>')
+                  and start_of_rpc_request):
+                if (start_of_rpc_request and end_of_rpc_request
+                    and not param in log_list[i]):
+                  end_of_rpc_request = True
+                  # rpc request scan completed, requested parameter not matched
+                  # this request is not request of interest
+                  start_of_rpc_request = False
+                  end_of_rpc_request = False
+                  break
+              # match queried parameter in rpc request
+              if start_of_rpc_request and not end_of_rpc_request:
+                if self.MatchParameterName(log_list[i], param, rpc):
+                  param_matching = True
+                  current_index = i
+                  break
+
+        # search for end of rpc request command
+        if param_matching and not end_of_rpc_request:
+          for i in range(current_index, len(log_list)-1):
+            if i < len(log_list):
+              # match rpc request string end
+              if self.FindEndofRequest(log_list, i, '</cwmp:' + rpc + '>'):
+                end_of_rpc_request = True
+                current_index = i
+                break
+
+        # search for rpc request reply message
+        if param_matching and end_of_rpc_request:
+          for i in range(current_index, len(log_list)-1):
+            if i < len(log_list):
+              if ('<soap:Fault>' in log_list[i] or '<soap:fault>' in log_list[i]
+                  or '<cwmp:' + rpc + 'Response>' in log_list[i]):
+                start_of_rpc_reply = True
+              if (' </soap:Fault>' in log_list[i]
+                  or ' </soap:fault>' in log_list[i]
+                  or '</cwmp:' + rpc + 'Response>' in log_list[i]):
+                end_of_rpc_reply = True
+                reply_msg.append(log_list[i].strip()+'\n')
+              if start_of_rpc_reply and not end_of_rpc_reply:
+                reply_msg.append(log_list[i].strip()+'\n')
+              if start_of_rpc_reply and end_of_rpc_reply:
+                return reply_msg
+    return ['No rpc message is available in device log']
+
+  def GetParameterListFromDeviceLog(self, time_stamp, param=''):
+    """Parse TR069 data model parameters from Device log information.
+
+    Args:
+      time_stamp: looking for log events that happened only after this time
+      param: the cwmp parameter to search in device log
+    Returns:
+      return parameter list if succeeded
+      return false otherwise
+    """
+    if param == '.':
+      param = ''
+    param_list = []
+    param_list_start = False
+    param_list_end = False
+    self.device_log_result = []
+
+    self.p_ssh.SendCmd('dmesg | grep cwmpd:')
+    log_list = self.p_ssh.GetCmdOutput()
+    log_list.reverse()
+
+    for line in log_list:
+      # check only the getParameter query starts after the last known event:
+      if float(self.GetTimeStamp(line)) > float(time_stamp):
+        if '<cwmp:GetParameterValues>' in line:
+          # check the parameter query from nbi_client has been answered:
+          m = re.search(r'.*<cwmp:GetParameterValues>.*<ParameterNames.*>(.*)'
+                        '</ParameterNames></cwmp:GetParameterValues>.*', line)
+          if m is not None and param in m.group(1):
+            index = log_list.index(line)
+            for i in range(index+1, index+50):
+              if '<cwmp:GetParameterValuesResponse>' in log_list[i]:
+                param_list_start = True
+                break
+            continue
+
+      if '</cwmp:GetParameterValuesResponse>' in line:
+        if param_list_start:
+          param_list_end = True
+          break
+
+      if param_list_start:
+        self.device_log_result.append(line.strip())
+        if '<Name>' in line:
+          output = re.sub(r'.*(<Name>)(.*)(</Name>)', r'\2', line)
+          output = output.strip()
+          output = re.sub(r'(\.)[\d]+(\.)', r'\1{i}\2', output)
+          if param_list.count(output) < 1:
+            param_list.append(output)
+
+    if (not param_list_start) or (not param_list_end):
+      error_msg = self.ParseDeviceLogForError(
+          time_stamp, log_list, param, 'GetParameterValues')
+      if 'fault' in error_msg[0] or 'Fault' in error_msg[0]:
+        # error occurred in rpc request
+        info = self.log.CreateErrorInfo(
+          'Critical', 'Exit! Error while get values: ' + ''.join(error_msg))
+        self.log.SendLines(self.test_info, info)
+        os.sys.exit(1)
+      else:
+        # no error in request, request still in progress
+        return False
+    return param_list
+
+  def CreateElement(self, param_name, param_type_device,
+                    param_type_nbi, param_value):
+    """Create a new parameter node that contains parameter name and value.
+
+    Currently try to set the same value as being read from the device.
+    Parameter type may have different representations on different platforms.
+    E.g.: type 'long_value' on nbi_client is 'unsignedInt' on bruno.
+          type 'string_value' on nbi_client is 'string' on bruno
+    Args:
+      param_name: the name of the parameter (Path)
+      param_type_device: the type of the parameter represented by device format
+      param_type_nbi: the type of parameter, represented by nbi_client format
+      param_value: the value of the parameter
+    Returns:
+      return the created element node
+    """
+    e = ET.Element('parameter_value_struct')
+    name = ET.Element('name')
+    name.text = param_name
+    e.append(name)
+    device_type = ET.Element('device_type')
+    device_type.text = param_type_device
+    e.append(device_type)
+    nbi_type = ET.Element('nbi_type')
+    if not param_type_nbi:
+      if param_type_device == 'unsignedInt':
+        param_type_nbi = 'long_value'
+      elif param_type_device == 'boolean':
+        param_type_nbi = 'boolean_value'
+      elif param_type_device == 'string':
+        param_type_nbi = 'string_value'
+      else:
+        info = self.log.CreateErrorInfo(
+            'Critical', 'Exit! Unknown (new) Parameter types found from device:'
+            ': ' + param_type_device + '. Define as string_value')
+        self.log.SendLine(self.test_info, info)
+        param_type_nbi = 'string_value'
+    nbi_type.text = param_type_nbi
+    e.append(nbi_type)
+    value = ET.Element('value')
+    value.text = param_value
+    e.append(value)
+    test_value = ET.Element('test_value')
+    test_value.text = param_value
+    e.append(test_value)
+    access_state = ET.Element('writable')
+    access_state.text = 'true'
+    e.append(access_state)
+    return e
+
+  def SortElementTree(self, tree):
+    """Sort the parameters in element tree, in alphabetical order of names.
+
+    Args:
+      tree: the ElementTree data structure that holds the parameter list
+    Returns:
+      return the sorted tree data structure
+    """
+    data = []
+    container = tree.findall('parameter_value_struct')
+    for node in container:
+      key = node.findtext('name')
+      data.append((key, node))
+
+    root = ET.Element(tree.getroot().tag, tree.getroot().attrib)
+    root.text = tree.getroot().text
+    data.sort()
+    for node in data:
+      root.append(node[1])
+    return ET.ElementTree(root)
+
+  def ParseToElementTree(self):
+    """Parse the parameter list to Element Tree structure."""
+    root = ET.Element('Bruno_Automation_Test', name='Set_Parameter_Value_Test',
+                      testCases=self.test_info['testID'])
+    root.text = 'This structure stores all parameters and values for this test'
+
+    if self.nbi_result:
+      for line in self.nbi_result:
+        if 'name:' in line:
+          param_name = re.sub('.*\"(.*)\".*', r'\1', line).strip()
+          index = self.nbi_result.index(line)
+          param_type = self.nbi_result[index+1].split(':')[0].strip()
+          param_value = self.nbi_result[index+1].split(':')[1].strip()
+          root.append(self.CreateElement(param_name, None,
+                                         param_type, param_value))
+    elif self.device_log_result:
+      #self.device_log_result.reverse()
+      for line in self.device_log_result:
+        if '<Name>' in line:
+          param_name = re.sub(r'.*(<Name>)(.*)(</Name>)', r'\2', line).strip()
+
+          index = self.device_log_result.index(line) + 1
+          while not 'Value' in self.device_log_result[index]:
+            index += 1
+            if index >= len(self.device_log_result) or '<Name>' in line:
+              info = self.log.createErrorInfo(
+                  'Critical', 'Mis-matching name-value pair in log file!')
+              self.log.sendLine(self.test_info, info)
+              return False
+
+          line = self.device_log_result[index]
+          # In case value string spans multiple lines:
+          while '</Value>' not in line:
+            index +=1
+            if index >= len(self.device_log_result):
+              info = self.log.CreateErrorInfo(
+                  'Critical', 'Exit! Cannot find matching </Value> tag in log!')
+              self.log.SendLine(self.test_info, info)
+              os.sys.exit(1)
+            new_line = self.device_log_result[index]
+            # if <Name> or EOF reached before a matching </Value> is found
+            if '<Name>' in new_line:
+              info = self.log.CreateErrorInfo(
+                  'Critical', 'Exit! Cannot find matching </Value> tag in log!')
+              self.log.SendLine(self.test_info, info)
+              os.sys.exit(1)
+            new_line = re.sub('\[\s*\d*\.\d{3}\]\s*cwmpd:\s*(.*)',
+                              r'\1', new_line).strip()
+            line += new_line.strip()
+
+          param_type = re.search('.*type\s*=\s*\"[a-zA-Z]*:([a-zA-Z]*)\">.*',
+                                 line, re.DOTALL).group(1)
+          param_value = re.search('.*<Value\s*[a-zA-Z]*:type="[a-zA-Z]*:'
+                                  '[a-zA-Z]*">(.*)</Value>',
+                                  line, re.DOTALL).group(1)
+          root.append(self.CreateElement(param_name, param_type,
+                                         None, param_value))
+    else:
+      info = self.log.CreateErrorInfo(
+          'Critical', 'Failed to get parameter list from device!')
+      self.log.SendLine(self.test_info, info)
+      return False
+
+    tree = ET.ElementTree(root)
+    tree = self.SortElementTree(tree)
+    return tree
+
+  def ParametersFromXMLFile(self, file_path=None):
+    """Read parameter data from an XML format file.
+
+    Args:
+      file_path: file path to the xml file
+    Returns:
+      return the parameter list stored in the file, in the format of ElementTree
+    """
+    f = None
+    try:
+      if not file_path:
+        f = open(self.params['para_tree_file'], 'r')
+      else:
+        f = open(file_path, 'r')
+      tree = ET.parse(f)
+    except IOError, inst:
+      err_string = ('Exit! Unexpected error opening '
+                    + str(f.name) + '. ' + str(inst))
+      info = self.log.CreateErrorInfo('Critical', err_string)
+      self.log.SendLine(self.test_info, info)
+      os.sys.exit(1)
+    return tree
+
+  def ParametersToXMLFile(self, tree=None, file_path=None):
+    """Output the available parameter instance tree to XML format file.
+
+    Parameter instance results will be written to XML formated file,
+    with test value that has been set for the parameter(s)
+    For those parameters that failed to set value, they are recorded in
+    the result file.
+
+    Args:
+      tree: the parameters structure to parse to file
+      file_path: path to the output file
+    """
+    if not tree:
+      tree = self.para_tree
+    if not file_path:
+      fout = open(self.params['para_tree_file'], 'w')
+    else:
+      fout = open(file_path, 'w')
+
+    try:
+      tree.write(fout)
+    except IOError, inst:
+      err_string = ('Exit! Unexpected error writing to file '
+                    + fout.name() + str(inst))
+      info = self.log.CreateErrorInfo('Critical', err_string)
+      self.log.SendLine(self.test_info, info)
+      os.sys.exit(1)
+    fout.close()
+
+  def GetRequirementFromFile(self):
+    """Get TR69 data model parameters from requirement file.
+
+    The format of the requirement file is as follows:
+      1. Each line represents a data model parameter
+      2. If a parameter has indexed nodes, index numbers are replaced by {i}
+        ###### Begin of File ######
+        Device.Bridging.
+        Device.Bridging.Bridge.{i}.
+        Device.Bridging.Bridge.{i}.Port.{i}.
+        Device.Bridging.Bridge.{i}.Port.{i}.Stats.
+        ... ... ...
+        Device.DeviceInfo.Manufacturer
+        Device.DeviceInfo.ManufacturerOUI
+        X_GOOGLE-COM_GVSB.EpgSecondary
+        X_GOOGLE-COM_GVSB.GvsbChannelLineup
+        X_GOOGLE-COM_GVSB.GvsbKick
+        X_GOOGLE-COM_GVSB.GvsbServer
+        ###### End of File ######
+    Returns:
+      return the requirement parameters in a list
+    """
+    req_list = []
+    for line in self.req_file:
+      line = line.strip()
+      if req_list.count(line) < 1:
+        req_list.append(line)
+    return req_list
+
+  def GetTimeStamp(self, line=None):
+    """Extract the time information of most current event from Device log.
+
+    i.e., the time that the last event happens since reboot (in seconds)
+    Args:
+      line: a line of device log which contains event time information.
+            If it has value of 'None', then retrieve the last a few lines from
+            device log to extract time information.
+    Returns:
+      return the time stamp string if succeed, return -1 otherwise.
+    """
+    if line is None:
+      # get time stamp from Device log file
+      self.p_ssh.SendCmd('dmesg | grep cwmpd:')
+      # search the last 30 lines in Device log for time information
+      latest_log_entry = 30
+      log_list = self.p_ssh.GetCmdOutput(latest_log_entry)
+
+      for line in log_list:
+        m = re.match('\\[[\s]*(\d*\\.\d{3})\\]', line)
+        if m is not None:
+          # Match, return the time stamp
+          return m.group(1)
+    else:
+      # get time stamp from a Device log line
+      m = re.match('\\[[\s]*(\d*\\.\d{3})\\]', line)
+      if m is not None:
+      # Match, return the time stamp
+        return m.group(1)
+    # No time stamp found
+    info = self.log.CreateErrorInfo(
+        'Critical', 'Cannot get time information from Device log!')
+    self.log.SendLine(self.test_info, info)
+    os.sys.exit(1)
+
+  def VerifyAllReqParameters(self):
+    """Check available parameters and compare to requirement list.
+
+    This method checks all available parameters on current image, and
+    compare the list with requirement list. It will find out:
+      1. the parameters that are missing from the requirement list.
+      2. the newly added parameters that are not in the requirement list.
+    Both lists are written to the result file.
+
+    This method also output all parameter instances on current device to
+    a xml formated file, together with their types and values
+    """
+    # get parameter name lists, compare with requirement list
+    info = self.log.CreateProgressInfo(
+        '30%', 'Get parameters from nbi_client ...')
+    self.log.SendLine(self.test_info, info)
+    time_stamp = self.GetTimeStamp()
+    chk_list = self.acs_instance.ParseParameterNames(self.GetParameterValues(),
+                                                     True)
+
+    if not chk_list:
+      info = self.log.CreateProgressInfo(
+        '35%', 'ACS failed to reply, get parameters from device log ...')
+      self.log.SendLine(self.test_info, info)
+      # nbi client may fail because of timeout
+      # if that is the case, then get parameter list from device log
+      count = 0
+      while not chk_list:
+        time.sleep(self.__delay)
+        chk_list = self.GetParameterListFromDeviceLog(time_stamp)
+        count += 1
+        if count*self.__delay >= self.__long_delay:
+          info = self.log.CreateErrorInfo(
+              'Critical', 'Can not retrieve complete log info from device')
+          self.log.SendLine(self.test_info, info)
+          os.sys.exit(1)
+
+        if not chk_list:
+          info = self.log.CreateProgressInfo(
+              '40%', 'GetParameterValue query in progress, retry after '
+              + str(self.__delay) + ' seconds.')
+          self.log.SendLine(self.test_info, info)
+      chk_list.sort()
+
+    info = self.log.CreateProgressInfo(
+        '55%', 'GetParameterValue query completed, verify parameter list '
+        'with parameter requirement document.')
+    self.log.SendLine(self.test_info, info)
+    req_list = self.GetRequirementFromFile()
+    (mis_list, new_list) = self.CheckRequirement(chk_list, req_list)
+    mis_list.sort()
+    new_list.sort()
+    self.log.SendTestData(['############# Missing Parameters #############'])
+    self.log.SendTestData(mis_list)
+
+    self.log.SendTestData(['############ Newly Added Parameters ############'])
+    self.log.SendTestData(new_list)
+
+    info = self.log.CreateProgressInfo(
+        '75%', 'Verify current parameter list with requirement completed. '
+        'Check result file:' + self.log.params['f_error'] + ' for details.')
+    self.log.SendLine(self.test_info, info)
+
+    # write parameter instance to file
+    self.para_tree = self.ParseToElementTree()
+    self.ParametersToXMLFile()
+    info = self.log.CreateProgressInfo(
+        '100%', 'Update parameter file: ' + self.params['para_tree_file']
+        + ' completed.')
+    self.log.SendLine(self.test_info, info)
+    info = self.log.CreateResultInfo(
+        'Pass', 'Verification completed. '
+        'Check result file: ' + self.log.params['f_result'] + ' for missing '
+        'parameters and new parameters.')
+    self.log.SendLine(self.test_info, info)
+
+  def GetParameterAccessStateFromDeviceLog(self, time_stamp=None, param='.'):
+    """Get parameter access state from device logs.
+
+    Args:
+      time_stamp: search events only after this time in log file
+      param: the parameter to search in device log
+    Returns:
+      return dictionary: parameter names as key, and writable status as value
+    """
+    self.p_ssh.SendCmd('dmesg | grep cwmpd:')
+    log_list = self.p_ssh.GetCmdOutput()
+    log_list.reverse()
+
+    param_state_dict = {}
+    param_list_start = False
+    param_list_end = False
+
+    for line in log_list:
+      # check only the getParameter query starts after the last known event:
+      if float(self.GetTimeStamp(line)) > float(time_stamp):
+        if '<cwmp:GetParameterNames>' in line:
+          # check the parameter query from nbi_client has been answered:
+          m = re.search(
+              r'.*<cwmp:GetParameterNames>.*<ParameterPath>(.*)'
+              '</ParameterPath>.*<NextLevel>false</NextLevel>.*'
+              '</cwmp:GetParameterNames>.*', line)
+          if m and param in line:
+            index = log_list.index(line)
+            for i in range(index+1, index+30):
+              if i < len(log_list):
+                if '<cwmp:GetParameterNamesResponse>' in log_list[i]:
+                  param_list_start = True
+                  break
+            continue
+
+      if '</cwmp:GetParameterNamesResponse>' in line:
+        if param_list_start:
+          param_list_end = True
+          break
+
+      if param_list_start:
+        if '<Name>' in line:
+          name = re.search(r'.*<Name>(.*)</Name>', line).group(1).strip()
+          index = log_list.index(line)+1
+          if '<Writable>' in log_list[index]:
+            access = re.sub(r'.*<Writable>(.*)</Writable>',
+                            r'\1', log_list[index]).strip()
+            if access == '1':
+              access = 'true'
+            else:
+              access = 'false'
+            param_state_dict.update({name: access})
+
+    if (not param_list_start) or (not param_list_end):
+      error_msg = self.ParseDeviceLogForError(
+          time_stamp, log_list, param, 'GetParameterNames')
+      if 'fault' in error_msg[0] or 'Fault' in error_msg[0]:
+        info = self.log.CreateErrorInfo(
+          'Critical', 'Exit! Error while get values: ' + ''.join(error_msg))
+        self.log.SendLines(self.test_info, info)
+        os.sys.exit(1)
+      else:
+        return False
+    return param_state_dict
+
+  def UpdateParameterAccessState(self, tree, param_name='.'):
+    """Update parameters' access state (read-only, read-write status).
+
+    TR69 parameters could be either in a read-only state, or a writable
+    state. The setParameterValues() test only verifies those parameters
+    in the 'writable' state. Try to set them with new values.
+    This method is called to determine the state of each supported parameter
+
+    Args:
+      tree:
+        the ElementTree data structure that holds the parameter attribute
+        list in XML format. This tree needs to be sorted before passed to
+        this function, according to the alphabetical order of parameter
+        names.
+      param_name:
+        the parameter name to update the access state
+    Returns:
+      returns this tree with updated parameter access state information
+    """
+    time_stamp = self.GetTimeStamp()
+    cmd_list = self.GetParameterNames(param_name, False)
+    if not self.acs_instance.CheckCmdCall(cmd_list):
+      info = self.log.CreateProgressInfo(
+            '---', 'GetParameterNames() query failed on ACS. Analyzing device'
+            ' logs...')
+      self.log.SendLine(self.test_info, info)
+
+      param_dict = self.GetParameterAccessStateFromDeviceLog(time_stamp,
+                                                             param_name)
+      while not param_dict:
+        info = self.log.CreateProgressInfo(
+            '---', 'GetParameterNames() analyze in progress, wait for '
+            + str(self.__short_delay) + ' seconds.')
+        self.log.SendLine(self.test_info, info)
+        time.sleep(self.__short_delay)
+        param_dict = self.GetParameterAccessStateFromDeviceLog(time_stamp,
+                                                               param_name)
+    else:
+      param_dict = self.acs_instance.ParseParameterAccessStates(cmd_list)
+      for key in param_dict.keys():
+        if param_name.endswith('.') and param_name != '.':
+          param_dict[param_name + key] = param_dict.pop(key)
+        else:
+          param_dict[param_name + '.' + key] = param_dict.pop(key)
+
+    container = tree.findall('parameter_value_struct')
+
+    for node in container:
+      name = node.findtext('name')
+      if name in param_dict.keys():
+        e = node.find('writable')
+        if not e:
+          # Create this tag:
+          e = ET.Element('writable')
+          e.text = param_dict[name]
+          node.append(e)
+        else:
+          e.text = param_dict[name]
+      else:
+        continue
+    return tree
+
+  def MarkParametersToSet(self, param_list, param):
+    """In the sorted tree, mark all parameters before param to read-only."""
+    if not param:
+      return param_list
+
+    ls = list(param_list)
+    for name in param_list:
+      if name != param:
+        ls.remove(name)
+      else:
+        break
+    return ls
+
+  def VerifySetAllParameterValues(self):
+    """This verifies the setParameterValues() on all supported parameters.
+
+    This method tests the 'set' capability on all parameters. According
+    to the data type, the value will be set as 'string', 'long_int', 'boolean',
+    etc. Note the current test only verify the 'set' action is supported by
+    each parameter. Therefore, it will try to set the same default value on each
+    parameter (it does not change the default value of each parameter).
+
+    This test assumes that the parameter list has already been retrieved from
+    device by 'GetparameterValues', and has been written to the parameter XML
+    file.
+    """
+    tree = self.ParametersFromXMLFile()
+    #tree = self.UpdateParameterAccessState(tree)
+    tree = self.SortElementTree(tree)
+    param_list = []
+    # Currently Assume all parameters are read-write
+    container = tree.findall('parameter_value_struct')
+
+    for node in container:
+      #name = node.findtext('name')
+      #test_value = node.findtext('test_value')
+      dev_type = node.findtext('device_type')
+      nbi_type = None
+      if dev_type == 'unsignedInt':
+        nbi_type = 'long_value'
+      elif dev_type == 'boolean':
+        nbi_type = 'boolean_value'
+      elif dev_type == 'string':
+        nbi_type = 'string_value'
+      else:
+        info = self.log.CreateErrorInfo(
+            'Critical', 'Exit! Unknown (new) Parameter types found from device:'
+            ': ' + dev_type)
+        self.log.SendLine(self.test_info, info)
+        os.sys.exit(1)
+      node.find('nbi_type').text = nbi_type
+      param_list.append(node.findtext('name'))
+      e = node.find('writable')
+      if not e:
+        access_state = ET.Element('writable')
+        access_state.text = 'true'
+        node.append(access_state)
+    param_list.sort()
+    param_list = self.MarkParametersToSet(
+        param_list, self.params['from_which_param_to_set'])
+    self.BatchSetParametersValues(tree, param_list)
+
+  def VerifyNTPTimeZoneConfig(self):
+    """Verify the Time zone configuration is received and processed by Device.
+
+    Check the log of device, to find TR69 parameter:
+    InternetGatewayDevice.Time.
+    is pushed down to device. Also verify the device correctly updated local
+    time information.
+    """
+    ## Blocked by bug: http://b/issue?id=6804551
+    ## TODO(lmeng): add set values and verify set values succeeded
+    self.p_ssh.SendCmd('dmesg | grep cwmpd:')
+    log_list = self.p_ssh.GetCmdOutput()
+    time_stamp = self.GetTimeStamp()
+    log_list = self.ParseDeviceLogForError(time_stamp, log_list,
+                                           'InternetGatewayDevice.Time',
+                                           'SetParameterValues')
+    if 'No rpc message is available in device log' in log_list[0]:
+      info = self.log.CreateResultInfo(
+        '---', 'Failed. Can not find TR69 Time zone information in device log')
+      self.log.SendLine(self.test_info, info)
+    elif 'fault' in log_list[0]:
+      info = self.log.CreateResultInfo(
+        '---', 'Failed to apply Time zone configuration on device.')
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateResultInfo(
+        '---', 'Succeed to setup Time zone on deivce.')
+      self.log.SendLine(self.test_info, info)
+
+  def VerifyManagementServerURL(self):
+    """Verify set and get Management Server URL from device."""
+    # This test is blocked by bug: http://b/issue?id=6813492
+    mngmt_url_param = 'InternetGatewayDevice.ManagementServer.URL'
+    url_from_dev_param = self.acs_instance.ParseManagementURL(
+        self.GetParameterValues(mngmt_url_param))
+    info = self.log.CreateProgressInfo(
+            '15%', 'Get management url from TR069 parameter: '
+            + mngmt_url_param + '. Value: ' + url_from_dev_param)
+    self.log.SendLine(self.test_info, info)
+    if not url_from_dev_param:
+      info = self.log.CreateProgressInfo(
+            '30%', 'Can not found Value for: '
+            'InternetGatewayDevice.ManagementServer.URL'
+            '. Verify to set default URL: ' + self.params['default_url'])
+      self.log.SendLine(self.test_info, info)
+      url_from_dev_param = self.params['default_url']
+
+    info = self.log.CreateProgressInfo(
+            '50%', 'Set parameter value for: '
+            'InternetGatewayDevice.ManagementServer.URL'
+            '. Value: ' + url_from_dev_param)
+    self.log.SendLine(self.test_info, info)
+    result = self.SetParameterValues(mngmt_url_param, 'string',
+                                     url_from_dev_param)
+    if not result:
+      info = self.log.CreateErrorInfo(
+          'Critical', 'Failed to set parameter: '
+          'InternetGatewayDevice.ManagementServer.URL')
+      self.log.SendLine(self.test_info, info)
+      info = self.log.CreateResultInfo(
+          'Failed', 'Failed to set parameter: '
+          'InternetGatewayDevice.ManagementServer.URL')
+      self.log.SendLine(self.test_info, info)
+    else:
+      info = self.log.CreateProgressInfo(
+          '100%', 'Succeeded to set parameter: '
+          'InternetGatewayDevice.ManagementServer.URL')
+      self.log.SendLine(self.test_info, info)
+      info = self.log.CreateResultInfo(
+          'Pass', 'Succeeded to set parameter: '
+          'InternetGatewayDevice.ManagementServer.URL')
+      self.log.SendLine(self.test_info, info)
+
+  def VerifySetParameterValuesRPC(self):
+    """Test case: 11722375. To verify SetParameterValues RPC.
+
+    To verify SetParametersRPC, this methond also try to set all parameters
+    which have 'write-able' status, with test value.
+    """
+    # update parameter access state:
+    info = self.log.CreateProgressInfo(
+        '20%', 'Read Parameters read/write status from file.')
+    self.log.SendLine(self.test_info, info)
+    tree = self.ParametersFromXMLFile()
+    info = self.log.CreateProgressInfo(
+        '50%', 'Update parameters read/write status from Device.')
+    self.log.SendLine(self.test_info, info)
+    tree = self.UpdateParameterAccessState(tree, self.params['param_path'])
+    self.ParametersToXMLFile(tree)
+    # set parameter values:
+    self.VerifySetAllParameterValues()
+    info = self.log.CreateProgressInfo(
+        '100%', 'Verify: Set values for all parameters completed.')
+    self.log.SendLine(self.test_info, info)
+    info = self.log.CreateResultInfo(
+        'Pass', 'Verification: set parameter '
+        'values completed. '
+        'Check result file: ' + self.log.params['f_result'] + ' for details.')
+    self.log.SendLine(self.test_info, info)
+
+  def VerifyMonitorTemperature(self):
+    """Test the temperature monitor on device.
+
+    This feature is not available for first launch.
+    """
+    info = self.log.CreateResultInfo(
+        'Block', 'This feature is not availalbe '
+        'on the first launch images.')
+    self.log.SendLine(self.test_info, info)
+
+  def VerifyAutoChannelEnable(self):
+    """Test AutoChannelEnable on device.
+
+    This feature is not available for first launch.
+    """
+    info = self.log.CreateResultInfo(
+        'Block', 'This feature is not availalbe '
+        'on the first launch images.')
+    self.log.SendLine(self.test_info, info)
+
+  def SetServiceConfigs(self, param_dict):
+    """Set service parameter profile on ACS.
+
+    This method sets the service configurations on ACS, and returns the
+    SetServiceConfigs result.
+    Args:
+      param_dict:
+        it is in this format in config file:
+        "wifi_params":{
+            "Google.Wifi.SSID":{"string_value":"Home"},
+            "Google.Wifi.Home.SSID_2GHz":{"string_value":"Home"},
+            "Google.Wifi.Enable":{"boolean_value":"true"},
+            "Google.Wifi.BeaconType":{"string_value": "Basic"},
+            "Google.Wifi.BasicEncryptionModes":{"string_value":"WEPEncryption"}
+    Returns:
+      True: if set succeeded
+      False: otherwise
+    """
+    app_id = self.GetACSAppFromURL()
+    nbi_path = self.params['nbi_client_path']
+    account_id = self.params['account_id']
+    device_label = self.params['device_label']
+    parameter_value_struct = ''
+
+    # parameter_value_struct: <name: "Google.Wifi.BasicEncryptionModes"
+    # string_value: "WEPEncryption">
+    for key in param_dict.iterkeys():
+      name = key
+      param_type = param_dict[key].keys()[0]
+      value = None
+      if 'string' in param_type:
+        value = '"' + param_dict[key].values()[0] + '"'
+      else:
+        value = param_dict[key].values()[0]
+      parameter_value_struct += (
+          ' parameter_value_struct: <name: "'
+          + name + '" ' + param_type +': ' + value + '> ')
+
+    parameter_value_struct = None
+    cmd = (nbi_path
+           + ' --server /gns/project/apphosting/stubby/prod-appengine/'
+           '*/' + app_id + '/default/* --service AccountService '
+           '--method SetServiceConfigs --request \''
+           'account_device_id: <account_id: "' + account_id
+           + '" device_label: "' + device_label + '"> parameter_list: < '
+           + parameter_value_struct + ' > async_apply_now: false\'')
+    print cmd
+    call_result = self.acs_instance.StubbyClientCall(cmd)
+    if 'FAILED' in call_result[0]:
+      info = self.log.CreateErrorInfo(
+          'Warning', 'ACS failed on SetServiceConfigs query: ' + cmd)
+      self.log.SendLines(self.test_info, info)
+      return False
+    else:
+      print call_result
+    return call_result
+
+  def VerifyWiFiConfiguration(self):
+    """Test case: 14165034, WiFi configuration verification.
+
+    This test case will set WiFi parameters on ACS, and verify
+    that the service profile is correctly pushed to device.
+    Thin Bruno is configured with correct WiFi network.
+
+    The WiFi parameters and values in specified in config file
+    in JSON format, e.g.:
+    "Google.Wifi.SSID":{"string_value":"Home"},
+    "Google.Wifi.Home.SSID_2GHz":{"string_value":"Home"},
+    "Google.Wifi.Enable":{"boolean_value":"true"},
+    "Google.Wifi.BeaconType":{string_value: "Basic"},
+    "Google.Wifi.BasicEncryptionModes":{"string_value":"WEPEncryption"}
+
+    Returns:
+      return False if test failed
+    """
+    info = self.log.CreateProgressInfo(
+          '10%', 'Setup SetServiceConfigs on ACS...')
+    self.log.SendLine(self.test_info, info)
+    if not self.SetServiceConfigs(self.params['wifi_params']):
+      return False
+    else:
+      info = self.log.CreateProgressInfo(
+          '20%', 'Setup SetServiceConfigs on ACS succeed.')
+      self.log.SendLine(self.test_info, info)
+
+    # Verify if the service is pushed to device
+    # TODO(lmeng): what parameters to set and to verify
+
+  #################### for Data Model Test Case  ####################
+
+  def Run(self):
+    """Run the test case."""
+    ####### Add your code here -- BEGIN #######
+    print 'Test Started...'
+    self.Init()
+    self.p_ssh.SshRetry(5, 15)
+    info = self.log.CreateProgressInfo(
+        '5%', 'SSH session to device successfully established!')
+    self.log.SendLine(self.test_info, info)
+
+    #check device profile/image is as erquired management url from ACS
+    self.TestEnvCheck()
+    info = self.log.CreateProgressInfo(
+        '10%', 'Check device test setup completed!')
+    self.log.SendLine(self.test_info, info)
+
+    if (self.test_info['testID'] == '14187024'
+        or self.test_info['testID'] == '14165033'
+        or self.test_info['testID'] == '14315008'
+        or self.test_info['testID'] == '14165035'
+        or self.test_info['testID'] == '14314007'
+        or self.test_info['testID'] == '11865133'
+        or self.test_info['testID'] == '14335001'
+        or self.test_info['testID'] == '14266498'
+        or self.test_info['testID'] == '14319149'
+        or self.test_info['testID'] == '14314008'):
+      self.VerifyAllReqParameters()
+    elif self.test_info['testID'] == '11722375':
+      self.VerifySetParameterValuesRPC()
+    elif self.test_info['testID'] == '14315009':
+      self.VerifyManagementServerURL()
+    elif self.test_info['testID'] == '14314009':
+      self.VerifyMonitorTemperature()
+    elif self.test_info['testID'] == '14165038':
+      self.VerifyAutoChannelEnable()
+    elif self.test_info['testID'] == '15643845':
+      self.VerifyNTPTimeZoneConfig()
+    elif self.test_info['testID'] == '14165034':
+      self.VerifyWiFiConfiguration()
+    else:
+      return
+    print 'Test Completed...'
+    ####### Add your code here -- END   #######
+
+  def Destructor(self):
+    self.p_ssh.Close()
+
+if __name__ == '__main__':
+  # Test cases defined in this file:
+  test_defined = ['14187024', '14165033', '14315008', '14165035', '14314007',
+                  '11865133', '14335001', '14266498', '14319149', '14314008',
+                  '11722375', '14315009', '14314009', '14165038', '15643845',
+                  '14165034']
+  # To execute test cases that defined in config file:
+  test = DataModelTest(testID='14165034')
+  test_to_execute = testCase.TestCase.FindTestCaseToExecute('config.cfg')
+  for test_id in test_to_execute:
+    if test_id in test_defined:
+      test = DataModelTest(testID=test_id)
diff --git a/device.py b/device.py
index c02fa33..f3eb65c 100644
--- a/device.py
+++ b/device.py
@@ -1,17 +1,24 @@
 #!/usr/bin/python
 # Copyright 2012 Google Inc. All Rights Reserved.
 
-"""This class defines devices."""
+"""This class defines devices.
 
-__author__ = 'Lin Xue (linxue@google.com)'
-
-"""This file defines the base class of all devices,
+   This file defines the base class of all devices,
    and specific device classes, such as Bruno, Spirent, etc.
 """
 
+__author__ = 'Lin Xue (linxue@google.com)'
+__author__ = 'Lehan Meng (lmeng@google.com)'
+
+
+import re
+
+import ip
+
 
 class Device(object):
   """Base class of all devices.
+
      information of device class:
      addr: address of device
      user: user name of device
@@ -23,24 +30,14 @@
      other: other information of device
   """
 
-  dev_info = {'addr': 'None',
-              'user': 'None',
-              'pwd': 'None',
-              'dev_name': 'None',
-              'cmd_prompt': 'None',
-              'other': 'None'}
-
   def GetDevInfo(self):
     """This function returns device information."""
-
     return (self.dev_info['dev_name'], self.dev_info['other'])
 
   def SetLogging(self, logging):
-    """This function set logging for device"""
-
+    """This function set logging for device."""
     self.log = logging
     return
-
 # End of Device class
 
 
@@ -48,13 +45,20 @@
   """Bruno device class."""
 
   def __init__(self, **kwargs):
-    """ Constructor for Bruno device
-        device name: Bruno, and other device informations
+    """Constructor for Bruno device.
+
+    device name: Bruno, and other device informations
     """
+    self.dev_info = {'addr': 'None',
+                     'user': 'None',
+                     'pwd': 'None',
+                     'dev_name': 'None',
+                     'cmd_prompt': 'None',
+                     'other': 'None'}
+
     self.dev_info['dev_name'] = 'Bruno'
     for s in kwargs:
-        self.dev_info[s] = kwargs[s]
-
+      self.dev_info[s] = kwargs[s]
     self.dev_ssh = None
 
     # Bruno Device attributes
@@ -76,26 +80,26 @@
     self.dev_epgpri = None
     # EPG secondary
     self.dev_epgsec = None
-
     return
 
   def GetDevCmd(self, cmd='', dev_attr='', dev_log=''):
     """This function sends command to Bruno.
+
        Then it gets back the results
        If success, save the results and write log
        If fail, show Warning message
-       Args:
-            cmd:
-                the command sent to Bruno system
-                to query the device information
-            dev_attr:
-                the device attribute
-                which can be get from this command
-            dev_log:
-                the logging message to be shown in log
-
+    Args:
+      cmd:
+        the command sent to Bruno system
+        to query the device information
+      dev_attr:
+        the device attribute
+        which can be get from this command
+      dev_log:
+        the logging message to be shown in log
+    Returns:
+      return the command results
     """
-
     # Send command to query Bruno device information
     self.dev_ssh.SendCmd(cmd)
 
@@ -111,8 +115,7 @@
                                          'Get ' + dev_log + ': ' + line)
       self.log.SendLine(None, info)
       dev_attr = line
-
-    return line
+    return line.strip()
 
   def GetSerialNumber(self):
     """This function returns Bruno device serial number."""
@@ -130,20 +133,107 @@
 
     return result
 
-  def GetIPAddress(self):
-    """This function returns Bruno device IP address."""
+  def VerifyCurrentVersion(self, version):
+    """Check if the current software version matches the given version.
 
+    Args:
+      version: the expected version string to check with device software
+    Returns:
+      True: if the current version matches the given version
+      False: if otherwise
+    """
+    if version == self.GetCurrentVersion():
+      return True
+    else:
+      return False
+
+  def GetIPaddress(self):
+    """This function returns Bruno device IP address."""
+    # Obsoleted
     result = self.GetDevCmd(r'ifconfig br0', self.dev_ip,
                             'IP address')
 
     return result
 
+  def GetDNSServer(self):
+    """This function returns device DNS server name."""
+    self.dev_ssh.SendCmd('cat /etc/resolv.conf')
+    cmd_list = self.dev_ssh.GetCmdOutput()
+    dns = None
+    for line in cmd_list:
+      m = re.search('nameserver (.*)', line)
+      if m:
+        dns = m.group(1)
+        break
+    return dns
+
+  def GetTimeServer(self):
+    """This function returns device DNS server name."""
+    self.dev_ssh.SendCmd('cat /etc/ntpd.conf')
+    cmd_list = self.dev_ssh.GetCmdOutput()
+    ts = None
+    for line in cmd_list:
+      m = re.search('server (.*)', line)
+      if m:
+        ts = m.group(1)
+        break
+    return ts
+
+  def GetGVSBHost(self):
+    """This function returns device GVSB host server name."""
+    self.dev_ssh.SendCmd('cat /tmp/gvsbhost')
+    cmd_line = self.dev_ssh.GetCmdOutput(2)[0]
+    m = re.search('http[s]?://[\w\d\./-]*', cmd_line)
+    if m:
+      return m.group(0)
+    return None
+
+  def GetIPv4Address(self):
+    """This function returns Bruno device IPv4 address."""
+    self.dev_ssh.SendCmd('ip addr show dev br0')
+    cmd_list = self.dev_ssh.GetCmdOutput()
+    address = None
+    for line in cmd_list:
+      if 'inet ' in line and 'scope global' in line:
+        address = ip.IPADDR(line).IsLikeIpv4Address()
+
+    if not address:
+      info = self.log.CreateErrorInfo('critical',
+                                      'Can not detect IPv4 address on device!')
+      self.log.SendLine(None, info)
+    return address
+
+  def GetIPv6Address(self):
+    """This function returns Bruno device IPv6 address.
+
+    This function will return a list of addresses. Sometimes device may
+    have multiple IPv6 addresses.
+
+    Returns:
+      return the obtained IPv6 address
+    """
+    self.dev_ssh.SendCmd('ip addr show dev br0')
+    cmd_list = self.dev_ssh.GetCmdOutput()
+    address = []
+    for line in cmd_list:
+      if 'inet6 ' in line and 'scope global' in line:
+        print line
+        m = re.match('inet6 ([\da-f:]+)/\d+ scope global \w*', line.strip())
+        if not m:
+          info = self.log.CreateErrorInfo(
+              'critical', 'Can not detect valid IPv6 address '
+              'on device! ' + line)
+          self.log.SendLine(None, info)
+          return address
+        addr = m.group(1)
+        address.append(addr)
+    return address
+
   def GetDevTime(self):
     """This function returns Bruno device current date."""
 
     result = self.GetDevCmd(r'date', self.dev_time,
                             'current date')
-
     return result
 
   def GetACSUrl(self):
@@ -151,9 +241,22 @@
 
     result = self.GetDevCmd(r'cat /tmp/cwmp/acs_url', self.dev_acs,
                             'ACS URL')
-
     return result
 
+  def VerifyCurrentACSURL(self, url):
+    """Check if the current ACS URL matches the given URL.
+
+    Args:
+      url: the expected url string to check with device ACS URL
+    Returns:
+      True: if the current url matches the given url
+      False: otherwise
+    """
+    if url == self.GetACSUrl():
+      return True
+    else:
+      return False
+
   def GetDnldImage(self):
     """This function returns Bruno device current downloading image."""
 
@@ -163,7 +266,7 @@
     return result
 
   def GetProductClass(self):
-    """This function returns Bruno device product class"""
+    """This function returns Bruno device product class."""
 
     result = self.GetDevCmd(r'cat /etc/platform', self.dev_class,
                             'product class')
@@ -171,7 +274,7 @@
     return result
 
   def GetEpgPrimary(self):
-    """This function returns Bruno device primary EPG"""
+    """This function returns Bruno device primary EPG."""
 
     result = self.GetDevCmd(r'cat /tmp/epgprimary', self.dev_epgpri,
                             'EPG primary')
@@ -179,7 +282,7 @@
     return result
 
   def GetEpgSecond(self):
-    """This function returns Bruno device secondary EPG"""
+    """This function returns Bruno device secondary EPG."""
 
     result = self.GetDevCmd(r'cat /tmp/epgsecondary', self.dev_epgsec,
                             'EPG secondary')
@@ -197,7 +300,7 @@
     # Other device informations
     self.GetSerialNumber()
     self.GetCurrentVersion()
-    self.GetIPAddress()
+    self.GetIPv4Address()
     self.GetDevTime()
     self.GetACSUrl()
     self.GetDnldImage()
@@ -207,6 +310,59 @@
 
     return
 
+  def GetTimeStamp(self, line=None):
+    """Extract the time information of most current event from Device log.
+
+    i.e., the time that the last event happens since reboot (in seconds)
+    Args:
+      line: a line of device log which contains event time information.
+            If it has value of 'None', then retrieve the last a few lines from
+            device log to extract time information.
+    Returns:
+      return the time stamp string if succeed, return -1 otherwise.
+    """
+    if not line:
+      # get time stamp from Device log file
+      self.dev_ssh.SendCmd('dmesg | grep cwmpd:')
+      log_list = self.dev_ssh.GetCmdOutput()
+
+      for line in log_list:
+        m = re.match('\\[[\s]*(\d*\\.\d{3})\\]', line)
+        if m:
+          # Match, return the time stamp
+          return m.group(1)
+    else:
+      # get time stamp from a Device log line
+      m = re.match('\\[[\s]*(\d*\\.\d{3})\\]', line)
+      if m:
+      # Match, return the time stamp
+        return m.group(1)
+    # No time stamp found
+    return -1
+
+  def FindLineInLog(self, log_list, time_stamp, pattern_line):
+    """Match a particular string from device logs.
+
+    Find a particular string or line from device log
+    Args:
+      log_list: the device log to search from
+      time_stamp: to search events that are after this time
+      pattern_line: the string or line that need to be matched in device log
+    Returns:
+      True: if 'line' matched
+      False: 'line' not found in device log
+    """
+    m = re.compile(pattern_line)
+    for line in log_list:
+      if float(self.GetTimeStamp(line)) > float(time_stamp):
+        #Debug
+        if 'detected' in line:
+          print 'here'
+
+        matching = m.search(line)
+        if matching:
+          return True
+    return False
 # End of Bruno device class
 
 
@@ -214,7 +370,8 @@
   """Spirent device class."""
 
   def __init__(self, **kwargs):
-    """ Constructor for Spirent device
+    """Constructor for Spirent device.
+
         device name: Spirent, and other device informations
     """
 
diff --git a/download.py b/download.py
index 475b287..d39b6e4 100755
--- a/download.py
+++ b/download.py
@@ -4,18 +4,11 @@
 """This class defines the test case "image download" from ACS.
 
 This class extends the TestCase Class.
-testID: 11843140
-This test does the following tasks:
-  - check current image version
-    - if the current version is the same as expected version,
-      this test will first to try to downgrade it to the downgrade_version.
-    - Then, it will Run image download test
-    - Fianlly, it will downgrade to downgrade_version if this has not been
-      done. This is to test the download handling capabilities of the
-      expected_version
-  - configure ACS instance with these software versions
-  - verify image download and installation to be complete and successful, by
-    observing a successfully reboot after installation
+This file includes the following test cases:
+  - testID: 11843140  7.6.11 Verify firmware upgrade/downgrade by ACS
+  - testID: 14165036  7.6.18 Negative test: Bruno Image download interruption
+  - testID: 14334001  7.6.25.3 File Transfer (File upload is optional, this is
+                               then basically the same test case as download.)
 """
 
 __author__ = 'Lehan Meng (lmeng@google.com)'
@@ -43,20 +36,17 @@
       title =Image_Download
       current_version=None
       expected_version=bruno-iguana-2-dg
-      ACS_URL = https://gfiber-acs-staging.appspot.com/cwmp
+      acs_url = https://gfiber-acs-staging.appspot.com/cwmp
   """
 
   def Init(self):
     """Initialte the DownloadTest instance."""
-    self.p_ssh = ssh.SSH(user=self.params['user'],
-                         addr=self.params['addr'],
-                         pwd=self.params['pwd'],
-                         bruno_prompt=self.params['bruno_prompt'],
-                         addr_ipv6=self.params['addr_ipv6'],
-                         athena_user=self.params['athena_user'],
-                         athena_pwd=self.params['athena_pwd'],
-                         jump_server=self.params['jump_server'])
-    self.p_ssh.Setlogging(self.log)
+    if not self.p_ssh:
+      self.p_ssh = ssh.SSH(user=self.params['user'],
+                           addr=self.params['addr'],
+                           bruno_prompt=self.params['bruno_prompt'],
+                           addr_ipv6=self.params['addr_ipv6'])
+      self.p_ssh.SetLogging(self.log)
     self.__debug = False
     self.__short_delay = 5  #short delay: 5 seconds
     self.__delay = 180  #medium delay: 180 seconds
@@ -69,18 +59,18 @@
     """
     self.p_ssh.SendCmd('cat /tmp/cwmp/acs_url')
     line = self.p_ssh.GetCmdOutput()[1]
-    m_1 = re.match(self.params['ACS_URL'], line)
+    m_1 = re.match(self.params['acs_url'], line)
     if m_1 is None:
       info = self.log.CreateErrorInfo(
           'intermediate',
           'ACS URL unknown! Update ACS URL manually')
       self.log.SendLine(self.test_info, info)
 
-      self.p_ssh.SendCmd('echo ' + self.params['ACS_URL']
+      self.p_ssh.SendCmd('echo ' + self.params['acs_url']
                          + ' > /tmp/cwmp/acs_url')
-      self.AddConfigParams('current_ACS_url', self.params['ACS_URL'])
+      self.AddConfigParams('current_ACS_url', self.params['acs_url'])
     else:
-      self.AddConfigParams('current_ACS_url', self.params['ACS_URL'])
+      self.AddConfigParams('current_ACS_url', self.params['acs_url'])
 
   def SetACSUrl(self, url='http://acs-sandbox.appspot.com/cwmp'):
     """Set ACS URL on the device."""
@@ -115,8 +105,7 @@
       # get time stamp from Device log file
       self.p_ssh.SendCmd('dmesg | grep cwmpd:')
       # search the last 30 lines in Device log for time information
-      latest_log_entry = 30
-      log_list = self.p_ssh.GetCmdOutput(latest_log_entry)
+      log_list = self.p_ssh.GetCmdOutput()
 
       for line in log_list:
         m = re.match('\\[[\s]*(\d*\\.\d{3})\\]', line)
@@ -131,30 +120,29 @@
         return m.group(1)
 
     # No time stamp found
-    return -1
+    info = self.log.createErrorInfo(
+              'Critical', 'Exit! Can not find time information from '
+              'device log ...')
+    self.log.sendLine(self.test_info, info)
+    return os.sys.exit(1)
 
-  def ConfigACS(self):
+  def ConfigACS(self, version_id):
     """Setup the ACS device profile with expected image.
 
     Then restart the cwmpd process on device to
     initiate the inform/download process
+    Args:
+      version_id: the expected version id that needs to be configured
     Returns:
       return the time stamp string if succeed, return -1 otherwise.
     """
     #configure ACS:
-    acs_instance = acs.ACS(self.params['expected_version_id'])
+    acs_instance = acs.ACS(acs_path=self.params['acs_path'])
     acs_instance.SetLogging(self.log)
-    acs_instance.SaveConfiguration()
+    acs_instance.SaveConfiguration(
+        self.params['profile_id'], version_id, self.params['acs_app_id'],
+        self.params['acs_host_name'])
 
-    #self.p_ssh.SendCmd('/etc/Init.d/S85catawampus restart')
-    # To setup any device service parameter using nbi_client will trigger
-    # ACS to make a remote connection request to the device.
-    # However, currently ACS does not provide separate stubby call to
-    # only request a remote connection.
-    # As walk around:
-    #   1. call remote management URL locally to initiate Inform immediately
-    #   2. wait for the next periodic inform to initiate download
-    # Here go for the 2nd option.
     time_stamp = self.GetTimeStamp()
     return time_stamp
 
@@ -220,30 +208,34 @@
       return True if rebooted
       return False otherwise
     """
+    time.sleep(5)
     s = self.GetTimeStamp()
-    if float(s) < float(time_stamp):
+    print 'previous time stamp: ' + time_stamp
+    print 'current time stamp: ' + s
+    if float(s) < float(time_stamp) and s is not -1:
       return True
     return False
 
-  def CheckImageInstallation(self, time_stamp):
+  def CheckImageInstallation(self, time_stamp, version):
     """Check if the Image installation and Device reboot is successful.
 
     An upgrade is considered complete only after the device is rebooted
     after image installation
     Args:
       time_stamp: check if a reboot is done after this time
+      version: the image version that needs to be installed
     Returns:
       return True if installation succeeded
       return False otherwise
     """
-    self.p_ssh.ExitToAthena()
-    time.sleep(3*self.__short_delay)
-    self.p_ssh.SshRetryFromAthena()
+    self.p_ssh.Close()
+    time.sleep(6*self.__short_delay)
+    self.p_ssh.SshRetry()
     count = 0
     ts = time_stamp
     while not self.IsRebooted(ts):
       count +=1
-      if count*self.__short_delay*3 > self.__long_delay:
+      if count*self.__short_delay*6 > self.__long_delay:
         info = self.log.CreateErrorInfo(
             'critical',
             'Error! File download longer than ' + str(self.__long_delay)
@@ -252,61 +244,56 @@
         os.sys.exit(1)
 
       t = self.GetTimeStamp()
-      if t > ts:
+      if float(t) > float(ts):
         ts = t
-      self.p_ssh.ExitToAthena()
+      self.p_ssh.Close()
       info = self.log.CreateProgressInfo(
-          '90%', 'Wait ' + str(3*self.__short_delay)
+          '---', 'Wait ' + str(6*self.__short_delay)
           + ' seconds for image installation and device reboot...')
       self.log.SendLine(self.test_info, info)
-      time.sleep(3*self.__short_delay)
-      self.p_ssh.SshRetryFromAthena()
+      time.sleep(6*self.__short_delay)
+      self.p_ssh.SshRetry()
 
     time.sleep(2*self.__short_delay)
     self.p_ssh.SendCmd('more /etc/version')
     line = self.p_ssh.GetCmdOutput()[1]
     crt_version = line.strip()
-    print crt_version + '########' + self.params['expected_version']
-    if crt_version == self.params['expected_version']:
+    print crt_version + '########' + version
+    if crt_version == version:
       return True
     else:
       return False
 
-  def Run(self):
-    """Run the test case."""
-    ####### Add your code here -- BEGIN #######
-    print 'Test Started...'
-    self.Init()
-    self.p_ssh.SshToAthena()
-    info = self.log.CreateProgressInfo(
-        '5%', 'SSH session to Athena server successfully established!')
-    self.log.SendLine(self.test_info, info)
+  def VerifyImageDownload(self):
+    """Verify image download and upgrade.
 
-    max_test_idx = 2
+    This test checks the following:
+      - check current image version
+      - if current_version is not expected_version as defined in config file:
+        - Upgrade to expected_version (verify expected version can boot up)
+        - Downgrade from expected_version to downgrade_version
+          (verify downgrade handling capability of expected_version image)
+        - Finally upgrade to expected_version, and keep device in this state.
+      - if the current version is already the expected_version,
+        - Downgrade from expected_version to downgrade_version
+          (verify downgrade handling capability of expected_version image)
+        - Finally upgrade to expected_version, and keep device in this state.
+
+    At the same time, it also verifies:
+      - configure ACS with these software versions
+    """
+    max_test_idx = 3
     current_test_idx = 1
+    upgrade = True
 
-    while current_test_idx < max_test_idx:
-      self.p_ssh.SshRetryFromAthena()
+    while current_test_idx <= max_test_idx:
       info = self.log.CreateResultInfo(
           '---', 'Test Sequence Number: ' + str(current_test_idx))
       self.log.SendLine(self.test_info, info)
-      while self.CheckDownloadFileExists():
-        # current in download
-        info = self.log.CreateProgressInfo(
-            '10%', 'Another downloading in progress, retry after '
-            + str(self.__short_delay) + 'seconds...')
-        self.log.SendLine(self.test_info, info)
-        self.p_ssh.ExitToAthena()
-        time.sleep(self.__short_delay)
-        self.p_ssh.SshRetryFromAthena()
-
-      info = self.log.CreateProgressInfo(
-          '15%', 'SSH session to device successfully established!')
-      self.log.SendLine(self.test_info, info)
 
       self.CheckACSUrl()
       info = self.log.CreateProgressInfo('20%', 'Current ACS url: '
-                                         + self.params['ACS_URL'])
+                                         + self.params['acs_url'])
       self.log.SendLine(self.test_info, info)
 
       self.CheckVersion()
@@ -314,47 +301,199 @@
                                          + self.params['current_version'])
       self.log.SendLine(self.test_info, info)
 
-      # Need to do:
-      # check if the currently version is already expected version,
-      # if so, downgrade first, then Run this test
-      #if self.params['current_version'] == self.params['expected_version']:
-      #  info = self.log.createErrorInfo(
-      #        'low', 'Currently image is expected version, '
-      #        + 'please try other images.')
-      #  self.log.sendLine(self.test_info, info)
+      if self.params['current_version'] == self.params['expected_version']:
+        if current_test_idx == 1:
+          current_test_idx += 1
+          upgrade = False
+          continue
 
-      self.time_stamp = self.ConfigACS()
-      info = self.log.CreateProgressInfo('40%', 'Expected Version: '
-                                         + self.params['expected_version'])
-      self.log.SendLine(self.test_info, info)
+      version = None
+      if upgrade:
+        self.time_stamp = self.ConfigACS(self.params['expected_version_id'])
+        info = self.log.CreateProgressInfo('40%', 'Expected Version: '
+                                           + self.params['expected_version'])
+        self.log.SendLine(self.test_info, info)
+        version = self.params['expected_version']
+      else:
+        self.time_stamp = self.ConfigACS(self.params['downgrade_version_id'])
+        info = self.log.CreateProgressInfo('40%', 'Downgrade Version: '
+                                           + self.params['downgrade_version'])
+        self.log.SendLine(self.test_info, info)
+        version = self.params['downgrade_version']
+
       info = self.log.CreateProgressInfo(
-          '40%', 'Successfully configured ACS for expected image version')
+          '50%', 'Successfully configured ACS for image version: ' + version)
       self.log.SendLine(self.test_info, info)
 
       time.sleep(self.__short_delay)  # time delay for log update
-      if self.CheckImageInstallation(self.time_stamp):
+      self.time_stamp = self.GetTimeStamp()
+      if self.CheckImageInstallation(self.time_stamp, version):
         info = self.log.CreateProgressInfo(
             '100%', 'Image successfully upgraded to expected version: '
             'from: ' + self.params['current_version']
-            + ' to: ' + self.params['expected_version'])
+            + ' to: ' + version)
         self.log.SendLine(self.test_info, info)
         info = self.log.CreateResultInfo(
             'Pass', 'Image successfully upgraded to expected version: '
             'from: ' + self.params['current_version']
-            + ' to: ' + self.params['expected_version'])
+            + ' to: ' + version)
         self.log.SendLine(self.test_info, info)
       else:
         info = self.log.CreateErrorInfo(
             'critical',
             'Error! Image version does not match expected version!')
         self.log.SendLine(self.test_info, info)
+        info = self.log.CreateResultInfo(
+            'Failed', 'Image failed to upgraded to expected version: '
+            'from: ' + self.params['current_version']
+            + ' to: ' + version)
+        self.log.SendLine(self.test_info, info)
 
       current_test_idx += 1
-      self.p_ssh.ExitToAthena()
-    # Need to do:
-    # downgrade from expected_version
-    # To verify the capability of the download handling on expected version
-    # image
+      if upgrade:
+        upgrade = False
+      else:
+        upgrade = True
+
+  def ImageUpgrade(self, expected_version, expected_version_id):
+    """This function upgrade device image to expected version.
+
+    Args:
+      expected_version: the version to be upgraded to
+      expected_version_id: the id of the expected version
+    Returns:
+      True if upgrade succeeds
+      False if upgrade fails
+    """
+    version = self.GetVersion()
+    if version == expected_version:
+      info = self.log.CreateProgressInfo(
+            '---', 'Image is already expected versioin: '
+            + expected_version)
+      self.log.SendLine(self.test_info, info)
+      return True
+    self.time_stamp = self.ConfigACS(expected_version_id)
+    info = self.log.CreateProgressInfo(
+          '---', 'Successfully configured ACS for image version: '
+          + expected_version)
+    self.log.SendLine(self.test_info, info)
+
+    time.sleep(self.__short_delay)  # time delay for log update
+    self.time_stamp = self.GetTimeStamp()
+    if self.CheckImageInstallation(self.time_stamp, expected_version):
+      info = self.log.CreateProgressInfo(
+            '---', 'Image successfully upgraded to expected version: '
+            'from: ' + version + ' to: ' + expected_version)
+      self.log.SendLine(self.test_info, info)
+      return True
+    else:
+      info = self.log.CreateErrorInfo(
+          'critical',
+          'Error! Image version does not match expected version!')
+      self.log.SendLine(self.test_info, info)
+      return False
+
+  def VerifyDownloadInterrupt(self):
+    """Negative test case: verify image download interruption.
+
+    This case tests device's capability of image upgrade in case of
+    power loss, power cycle, or multiple download requests from ACS.
+    The device should finally be able to complete the download and
+    image upgrade process from expected_version to downgrade_version.
+
+    Returns:
+      return: False if the verification fails
+      return: True otherwise
+    """
+    self.CheckVersion()
+    if self.params['current_version'] != self.params['expected_version']:
+      info = self.log.CreateProgressInfo(
+          '5%', 'Current version is not expected version to test, upgrade to: '
+          + self.params['expected_version'])
+      self.log.SendLine(self.test_info, info)
+      if not self.ImageUpgrade(self.params['expected_version'],
+                               self.params['expected_version_id']):
+        info = self.log.CreateErrorInfo(
+            'critical',
+            'Failed to upgrade version to: '
+            + self.params['expected_version'])
+        self.log.SendLine(self.test_info, info)
+        return False
+    self.ConfigACS(self.params['downgrade_version_id'])
+    info = self.log.CreateProgressInfo(
+          '10%', 'Successfully configured ACS for image version: '
+          + self.params['downgrade_version'])
+    self.log.SendLine(self.test_info, info)
+
+    time.sleep(6*self.__short_delay)
+
+    info = self.log.CreateProgressInfo(
+          '40%', 'Download started, reboot the device.')
+    self.log.SendLine(self.test_info, info)
+    time_stamp = self.GetTimeStamp()
+    self.p_ssh.SendCmd('reboot')
+    self.p_ssh.GetCmdOutput()
+    while not self.IsRebooted(time_stamp):
+      self.p_ssh.Close()
+      time.sleep(self.__short_delay*3)
+      self.p_ssh.SshRetry()
+      info = self.log.CreateProgressInfo(
+          '60%', 'Downloading interrupted, waiting for device boot up.')
+      self.log.SendLine(self.test_info, info)
+
+    time_stamp = self.GetTimeStamp()
+    version = self.GetVersion()
+    if version != self.params['downgrade_version']:
+      count = 0
+      while not self.IsRebooted(time_stamp):
+        count += 1
+        if count*self.__short_delay > self.__long_delay:
+          info = self.log.CreateErrorInfo(
+              'critical',
+              'Error! Image downloading is not successful after interruption!')
+          self.log.SendLine(self.test_info, info)
+          info = self.log.CreateResultInfo(
+              'Failed',
+              'Image failed to upgraded to version: '
+              + self.params['downgrade_version'] + ' ('
+              + self.__long_delay + ' seconds) after interrupted.')
+          self.log.SendLine(self.test_info, info)
+          return False
+        time_stamp = self.GetTimeStamp()
+        info = self.log.CreateProgressInfo(
+          '80%', 'Waiting for downloading complete after interruption.')
+        self.log.SendLine(self.test_info, info)
+        time.sleep(self.__short_delay*5)
+
+    info = self.log.CreateProgressInfo(
+        '100%', 'Image successfully upgraded to version: '
+        + self.params['downgrade_version'] + ' after interrupted.')
+    self.log.SendLine(self.test_info, info)
+    info = self.log.CreateResultInfo(
+        'Pass', 'Image successfully upgraded to version: '
+        + self.params['downgrade_version'] + ' after interrupted.')
+    self.log.SendLine(self.test_info, info)
+    return True
+
+  def Run(self):
+    """Run the test case."""
+    ####### Add your code here -- BEGIN #######
+    print 'Test Started...'
+    self.Init()
+    self.p_ssh.SshRetry()
+    info = self.log.CreateProgressInfo(
+        '5%', 'SSH session to device successfully established!')
+    self.log.SendLine(self.test_info, info)
+
+    if self.test_info['testID'] == '11843140':
+      self.VerifyImageDownload()
+      info = self.log.CreateResultInfo(
+        '----', 'For file transfer test, refer to image download test result:'
+        ' testID: 11843140.')
+      self.log.SendLine(self.test_info, info)
+    elif self.test_info['testID'] == '14165036':
+      self.VerifyDownloadInterrupt()
+
     print 'Test Completed...'
     ####### Add your code here -- END   #######
 
@@ -365,4 +504,10 @@
 
 
 if __name__ == '__main__':
-  DownloadTest(testID='11843140', key_word='Download')
\ No newline at end of file
+  # Test cases defined in this file:
+  test_defined = ['11843140', '14165036', '14334001']
+  # To execute test cases that defined in config file:
+  test_to_execute = testCase.TestCase.FindTestCaseToExecute('config.cfg')
+  for test_id in test_to_execute:
+    if test_id in test_defined:
+      test = DownloadTest(testID=test_id)
diff --git a/logging.py b/logging.py
old mode 100644
new mode 100755
index ade9f50..dd1654a
--- a/logging.py
+++ b/logging.py
@@ -16,17 +16,13 @@
 
 
 import datetime
+import errno
+import os
 
 
 class Logging(object):
   """This Class collects and writes test information to log files."""
 
-  params = {'std_out': '1',
-           'f_result': 'result.log',
-           'f_error': 'error.log',
-           'f_progress': 'progress.log',
-           'delimiter': '\t'}
-
   def __init__(self, **kwargs):
     """initiate the logging instance.
 
@@ -36,15 +32,129 @@
       kwargs['f_progres']: file name for test progres
       kwargs['std_out']: by default, send a copy of the log info to stdout
     """
-    for s in ('f_result', 'f_error', 'f_progress', 'std_out'):
-      if s in kwargs:
-        self.params[s] = kwargs[s]
 
-    self.rst_file = open(self.params['f_result'], 'a')
-    self.err_file = open(self.params['f_error'], 'a')
-    self.prg_file = open(self.params['f_progress'], 'a')
+    self.params = {'std_out': '1',
+                   'f_result': 'result.log',
+                   'f_error': 'error.log',
+                   'f_progress': 'progress.log',
+                   'delimiter': '\t',
+                   'out_folder': 'log',
+                   'in_folder': 'data'}
+    for s in kwargs:
+      self.params[s] = kwargs[s]
+
+    try:
+      os.makedirs(self.params['out_folder'])
+    except OSError, e:
+      if e.errno != errno.EEXIST:
+        raise
+
+    now = datetime.datetime.now()
+    s_time = now.strftime('%Y-%m-%d_%H:%M:%S.%f')
+    for f_name in ['f_result', 'f_error', 'f_progress']:
+      name = self.ParseLogFileName(self.params[f_name], 'w', s_time)
+      if name:
+        self.params[f_name] = name
+      else:
+        print 'Error! Fail to create log File.'
+    self.rst_file = open(self.params['f_result'], 'w+')
+    self.err_file = open(self.params['f_error'], 'w+')
+    self.prg_file = open(self.params['f_progress'], 'w+')
     self.NewTestStart()
 
+  def CreateFile(self, f_name, access='r'):
+    """Create a file for read/write.
+
+    Args:
+      f_name: the file name that needs to be created
+      access: 'r': read, input file
+              'w': write, output file
+              'rw': read and write
+    Returns:
+      f_obj: return the file object
+             if not succeed, return False
+    """
+    f_obj = None
+    if access == 'r':
+      # for input file
+      try:
+        f_obj = open(f_name, 'r')
+      except IOError:
+        print 'Cannot open the input file, return'
+        return False
+    elif access == 'w':
+      # for input file
+      try:
+        os.makedirs(self.params['out_folder'])
+      except OSError, e:
+        if e.errno != errno.EEXIST:
+          print 'Cannot create folder! Return'
+          return False
+      full_path = self.ParseLogFileName(f_name, access)
+      if full_path:
+        try:
+          f_obj = open(full_path, 'w+')
+        except IOError:
+          print 'Cannot open the given file, return'
+          return False
+      else:
+        # Can not parse file name
+        print 'Cannot parse given file name'
+        return False
+    else:
+      # read-write file:
+      try:
+        f_obj = open(f_name, 'r')
+      except IOError:
+        print 'Cannot open the input file, return'
+        return False
+    return f_obj
+
+  def ParseLogFileName(self, f_name, access='r', time_stamp=None):
+    """Generate an uniq log file name for each test case instance.
+
+    Args:
+      f_name: file name
+      access: 'r': read mode
+              'w': write mode
+              'rw': read-write
+      time_stamp: the time tag used for file name
+                  if None, create a new tag using current time
+    Returns:
+      return the parsed file name
+    """
+    if not f_name:
+      f_name = 'logFile.log'
+    if not time_stamp:
+      now = datetime.datetime.now()
+      s_time = now.strftime('%Y-%m-%d_%H:%M:%S.%f')
+    else:
+      s_time = time_stamp
+
+    ls = f_name.split('.')
+    ext_name = ''
+    name = ''
+    if len(ls) == 1:
+      name = ls[0]
+    else:
+      ext_name = ls.pop(-1)
+      for s in ls:
+        name += (s+'.')
+      name = name.rstrip('.')
+
+    if not ext_name:
+      ext_name = 'log'
+    folder = None
+    if access == 'r':
+      folder = self.params['in_folder']
+    elif access == 'w':
+      folder = self.params['out_folder']
+    else:
+      folder = self.params['in_folder']
+    name = (folder + '/' + name + '_testID-' + self.params['testID']
+            + '_'+ s_time + '.' + ext_name)
+    return name
+
   def SendLineToResult(self, test_info, result_info):
     """Send test result (one-line) information to the result file.
 
@@ -56,7 +166,7 @@
     """
     now = datetime.datetime.now()
     if test_info is not None:
-      line = (now.strftime('%Y-%m-%d %H:%M') + self.params['delimiter']
+      line = (now.strftime('%Y-%m-%d %H:%M:%S.%f') + self.params['delimiter']
               + 'testID:' + test_info['testID']
               + self.params['delimiter'] + 'TestStartAt:'
               + test_info['start_time']
@@ -69,7 +179,8 @@
 
     if self.params['std_out'] > 0:
       print line
-    return self.rst_file.write(line)
+    self.rst_file.write(line)
+    self.rst_file.flush()
 
   def NewTestStart(self):
     """Mark the start of a new test in the file."""
@@ -91,7 +202,7 @@
     """
     now = datetime.datetime.now()
     if test_info is not None:
-      line = (now.strftime('%Y-%m-%d %H:%M') + self.params['delimiter']
+      line = (now.strftime('%Y-%m-%d %H:%M:%S.%f') + self.params['delimiter']
               + 'testID:' + test_info['testID'] + self.params['delimiter']
               + 'TestStartAt:' + test_info['start_time']
               + self.params['delimiter'] + 'Keyword:'
@@ -99,13 +210,14 @@
               + error_info['severity'] + self.params['delimiter']
               + error_info['note'] + '\n')
     else:
-      line = (now.strftime('%Y-%m-%d %H:%M') + self.params['delimiter'] +
+      line = (now.strftime('%Y-%m-%d %H:%M:%S.%f') + self.params['delimiter'] +
               error_info['severity'] + self.params['delimiter']
               + error_info['note'] + '\n')
 
     if self.params['std_out'] > 0:
       print line
-    return self.err_file.write(line)
+    self.err_file.write(line)
+    self.err_file.flush()
 
   def SendLineToProgress(self, test_info, progress_info):
     """Send test progress (one-line) information to the progress file.
@@ -119,20 +231,21 @@
     """
     now = datetime.datetime.now()
     if test_info is not None:
-      line = (now.strftime('%Y-%m-%d %H:%M') + self.params['delimiter']
+      line = (now.strftime('%Y-%m-%d %H:%M:%S.%f') + self.params['delimiter']
               + 'testID:' + test_info['testID'] + self.params['delimiter']
               + 'TestStartAt:'+ test_info['start_time']
               + self.params['delimiter'] + 'Keyword:' + test_info['key_word']
               + self.params['delimiter'] + progress_info['percent']
               + self.params['delimiter'] + progress_info['note'] + '\n')
     else:
-      line = (now.strftime('%Y-%m-%d %H:%M') + self.params['delimiter']
+      line = (now.strftime('%Y-%m-%d %H:%M:%S.%f') + self.params['delimiter']
               + progress_info['percent'] + self.params['delimiter']
               + progress_info['note'] + '\n')
 
     if self.params['std_out'] > 0:
       print line
-    return self.prg_file.write(line)
+    self.prg_file.write(line)
+    self.prg_file.flush()
 
   def SendLine(self, test_info, info):
     """Send test (one-line) information to the corresponding log file.
@@ -151,12 +264,18 @@
     elif info['type'] == 'progress':
       return self.SendLineToProgress(test_info, info)
 
+  def SendTestData(self, data_list):
+    """Send test data to result file."""
+    for line in data_list:
+      self.rst_file.write(line + '\n')
+    self.err_file.flush()
+
   def SendLines(self, test_info, info):
     """Send test output (multiple lines) to the corresponding log file.
 
     Args:
       test_info: test case related info: testID, start_time, key_word, etc
-      info: test results/error/progress information (single line)
+      info: test results/error/progress information
             test_info could be "None" for class other than TestCase
     Returns:
       return 1 when succeed
@@ -242,4 +361,4 @@
     """Close the log files."""
     self.rst_file.close()
     self.err_file.close()
-    self.prg_file.close()
\ No newline at end of file
+    self.prg_file.close()
diff --git a/run.py b/run.py
index b0e5088..1130dc7 100755
--- a/run.py
+++ b/run.py
@@ -17,7 +17,7 @@
 optspec = """
 run [options...] <hosts> -- <command string...>
 --
-r,raw           Raw output (don't prefix with the host's name)
+r,raw           Raw output (don't redirect stdin/stdout/stderr)
 q,quiet         Quiet: don't print to stderr for nonzero exit codes
 """
 
@@ -42,27 +42,32 @@
 
 
 class Handler(object):
-  def __init__(self, prefix, fileobj, raw):
+  """When data is received from the given file, print it line by line."""
+
+  def __init__(self, prefix, fileobj):
     self.prefix = prefix
     self.fileobj = fileobj
-    self.raw = raw
+    self.buf = ''
+
+  def __del__(self):
+    if self.buf and not self.buf.endswith('\n'):
+      self.buf += '\n'
+    self.PrintBuf()
 
   def fileno(self):  #gpylint: disable-msg=C6409
     return self.fileobj.fileno()
 
+  def PrintBuf(self):
+    while '\n' in self.buf:
+      line, self.buf = self.buf.split('\n', 1)
+      print '%s: %s' % (self.prefix, line)
+
   def Run(self):
     """Read from this handler and write its results to stdout."""
-    buf = os.read(self.fileobj.fileno(), 65536)
-    if buf:
-      if self.raw:
-        sys.stdout.write(buf)
-      else:
-        if not buf.endswith('\n'):
-          buf += '\n'
-        lines = buf.split('\n')[:-1]
-        for line in lines:
-          print '%s: %s' % (self.prefix, line)
-    return buf
+    got = os.read(self.fileobj.fileno(), 65536)
+    self.buf += got
+    self.PrintBuf()
+    return got
 
 
 def main():
@@ -95,11 +100,14 @@
     else:
       argv = cmd[0]
       shell = True
-    p = subprocess.Popen(argv, env=env, shell=shell,
-                         stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+    if opt.raw:
+      p = subprocess.Popen(argv, env=env, shell=shell)
+    else:
+      p = subprocess.Popen(argv, env=env, shell=shell,
+                           stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+      handlers.append(Handler(host.name, p.stdout))
+      handlers.append(Handler(host.name, p.stderr))
     procs.append((host, p))
-    handlers.append(Handler(host.name, p.stdout, opt.raw))
-    handlers.append(Handler(host.name, p.stderr, opt.raw))
 
   while handlers:
     r, _, _ = select.select(handlers, [], [])
diff --git a/ssh.py b/ssh.py
old mode 100644
new mode 100755
index 8b9f2e3..7232542
--- a/ssh.py
+++ b/ssh.py
@@ -54,9 +54,10 @@
       'bruno_prompt': '\(none\)#',
       'jump_server': 'jmp.googlefiber.net',
       'jump_prompt': None,
-      'athena_user': None,
-      'athena_pwd': None,
-      'athena_prompt': None}
+      'athena_user': 'lmeng',
+      'athena_pwd': 'changeme',
+      'athena_prompt': None,
+      'ssh_use_athena': False}
 
     self.tmp_file = tempfile.TemporaryFile(mode='w+t')
 
@@ -66,19 +67,13 @@
 
     for s in kwargs:
       self.params[s] = kwargs[s]
-
     #populate prompt on jump and athena server:
-    if self.params['athena_user'] is not 'None':
+    if self.params['athena_user']:
       self.params['jump_prompt'] = self.params['athena_user'] + '@jmp101.nuq1:'
       self.params['athena_prompt'] = ('[' + self.params['athena_user']
                                       + '@athenasrv3 ~]$')
 
-    if dev is not None:
-      # parameters used to open an ssh tunnel to this device
-      for s in ('user', 'addr', 'pwd', 'bruno_prompt', 'addr_ipv6'):
-        self.params[s] = kwargs[s]
-
-  def Setlogging(self, logging):
+  def SetLogging(self, logging):
     """Setup the logging file(s)."""
     self.log = logging
 
@@ -102,7 +97,7 @@
     p_ssh = pexpect.spawn('ssh '+self.params['user']+'@'+addr)
     i = p_ssh.expect(
         [ssh_newkey, 'password:', self.params['bruno_prompt'], pexpect.EOF,
-         '\(none\)#', 'gfibertv#', pexpect.TIMEOUT])
+         '\(none\)#', 'gfibertv#', pexpect.TIMEOUT], self.__long_delay)
 
     while 1:
       if i == 0:
@@ -123,25 +118,14 @@
         if i == 5: self.params['bruno_prompt'] = 'gfibertv#'
         return True
       if i == 3 or i == 6:
-        print 'Key or connection timeout'
+        print 'ssh did not respond, or connection timeout'
         return False
       if i < 0 or i > 6:
         print 'Error, quit'
         return False
 
-  def SshFromAthena(self):
-    """Initiate an ssh tunnel from the athenasrv3 to the device.
-
-    Returns:
-      return True when succeed, return False otherwise
-    """
-    # clear the temp file
-    self.tmp_file.close()
-    self.tmp_file = tempfile.TemporaryFile(mode='w+t')
-    self.p_ssh.logfile = self.tmp_file
-    #Const
-    ssh_newkey = 'Are you sure you want to continue connecting'
-
+  def SetDeviceSSHKey(self):
+    """Setup ssh-agent on athena server."""
     self.p_ssh.sendline('ssh-agent /bin/bash')
     self.p_ssh.expect(self.params['athena_prompt'])
     #p_ssh.expect(self.params['athena_prompt'])
@@ -153,6 +137,24 @@
     self.p_ssh.expect(self.params['athena_prompt'])
     #print self.p_ssh.before
 
+  def SshFromAthena(self, set_key=False):
+    """Initiate an ssh tunnel from the athenasrv3 to the device.
+
+    Args:
+      set_key: if set True, ssh-key will be reset before ssh
+    Returns:
+      return True when succeed, return False otherwise
+    """
+    # clear the temp file
+    self.tmp_file.close()
+    self.tmp_file = tempfile.TemporaryFile(mode='w+t')
+    self.p_ssh.logfile = self.tmp_file
+    #Const
+    ssh_newkey = 'Are you sure you want to continue connecting'
+
+    if set_key:
+      self.SetDeviceSSHKey()
+
     if self.params['addr_ipv6']:
       addr = self.params['addr_ipv6']
     else:
@@ -189,8 +191,8 @@
 
   def ExitToAthena(self):
     """Terminate the ssh tunnel to device, and exit to athenasrv3."""
-    self.p_ssh.sendline('exit')
-    self.p_ssh.expect(self.params['athena_prompt'])
+    #self.p_ssh.sendline('exit')
+    #self.p_ssh.expect(self.params['athena_prompt'])
     self.p_ssh.sendline('exit')
     self.p_ssh.expect(self.params['athena_prompt'])
 
@@ -203,12 +205,13 @@
     Returns:
       return True upon success
     """
+    self.params['ssh_use_athena'] = True
     p_ssh = pexpect.spawn('ssh -a ' + self.params['jump_server'])
     print 'ssh -a jmp.googlefiber.net ...'
     #p_ssh.expect('lmeng@jmp101.nuq1:.*[\\$]')
     i = p_ssh.expect(
         [self.params['jump_prompt'], pexpect.TIMEOUT, pexpect.EOF],
-        self.__short_delay)
+        self.__short_delay*3)
     if i == 1 or i == 2:
       info = self.log.CreateErrorInfo(
             'critical', 'Cannot establish SSH tunnel to jump server.'
@@ -254,7 +257,7 @@
             + str(delay) + ' seconds')
         self.log.SendLine(None, info)
         time.sleep(delay)
-        tunnel = self.p_ssh.Ssh()
+        tunnel = self.Ssh()
         retry += 1
 
       if retry >= max_retry:
@@ -269,14 +272,15 @@
         '---', 'ssh session to Device successfully established!')
     self.log.SendLine(None, info)
 
-  def SshRetryFromAthena(self, max_retry=5, retry_delay=15):
+  def SshRetryFromAthena(self, max_retry=5, retry_delay=15, set_key=False):
     """Establish ssh connection to Device from athenasrv3, retry upon failure.
 
     Args:
       max_retry: the total number of retry
       retry_delay: delay between each ssh try
+      set_key: if ssh-key needs to be set
     """
-    tunnel = self.SshFromAthena()
+    tunnel = self.SshFromAthena(set_key)
     if not tunnel:
       retry = 0
       while not tunnel and retry < max_retry:
@@ -301,11 +305,19 @@
         '---', 'ssh session to Device successfully established!')
     self.log.SendLine(None, info)
 
-  def SendCmd(self, cmd):
-    """Send a command to the Device over ssh tunnel."""
+  def SendCmd(self, cmd, delay=10):
+    """Send a command to the Device over ssh tunnel.
+
+    Args:
+      cmd: the shell command to execute
+      delay: time delay while waiting for shell prompt after command is
+             executed
+    Returns:
+      return True if send command succeeded
+    """
     self.p_ssh.sendline(cmd)
     i = self.p_ssh.expect(
-        [self.params['bruno_prompt'], pexpect.EOF, pexpect.TIMEOUT], 10)
+        [self.params['bruno_prompt'], pexpect.EOF, pexpect.TIMEOUT], delay)
 
     while 1:
       if i == 0:
@@ -315,13 +327,16 @@
         # prompt not returned
         info = self.log.CreateErrorInfo(
             'Warning', 'Device not responding to shell command or timeout.'
-            ' Connect after 3 seconds ...')
+            ' Connect after 10 seconds ...')
         self.log.SendLine(None, info)
-        time.sleep(3)
-        self.SshRetryFromAthena()
+        time.sleep(10)
+        if self.params['ssh_use_athena']:
+          self.SshRetryFromAthena()
+        else:
+          self.SshRetry()
         self.p_ssh.sendline(cmd)
         i = self.p_ssh.expect(
-            [self.params['bruno_prompt'], pexpect.EOF, pexpect.TIMEOUT], 5)
+            [self.params['bruno_prompt'], pexpect.EOF, pexpect.TIMEOUT], delay)
 
   def GetCmdOutput(self, buff_size=0):
     """Get lines from the command output of the device, in a bottom up manner.
@@ -367,18 +382,18 @@
   def Close(self):
     # exit the ssh tunnel
     self.p_ssh.sendline('exit')
-    i = self.expect([self.p_ssh.params['athena_prompt'],
-                     self.p_ssh.params['jump_prompt'],
-                     self.p_ssh.params['bruno_prompt'],
-                     pexpect.EOF, pexpect.TIMEOUT])
+    i = self.p_ssh.expect([self.params['athena_prompt'],
+                           self.params['jump_prompt'],
+                           self.params['bruno_prompt'],
+                           pexpect.EOF, pexpect.TIMEOUT])
     while 1:
       if i == 0:
         # now at athena server:
         self.p_ssh.sendline('exit')
-        i = self.expect([self.p_ssh.params['athena_prompt'],
-                         self.p_ssh.params['jump_prompt'],
-                         self.p_ssh.params['bruno_prompt'],
-                         pexpect.EOF, pexpect.TIMEOUT])
+        i = self.p_ssh.expect([self.params['athena_prompt'],
+                               self.params['jump_prompt'],
+                               self.params['bruno_prompt'],
+                               pexpect.EOF, pexpect.TIMEOUT])
       elif i == 1:
         # now at jump server:
         self.p_ssh.sendline('exit')
@@ -386,21 +401,20 @@
       elif i == 2:
         # now at bruno:
         self.p_ssh.sendline('exit')
-        i = self.expect([self.p_ssh.params['athena_prompt'],
-                         self.p_ssh.params['jump_prompt'],
-                         self.p_ssh.params['bruno_prompt'],
+        i = self.expect([self.params['athena_prompt'],
+                         self.params['jump_prompt'],
+                         self.params['bruno_prompt'],
                          pexpect.EOF, pexpect.TIMEOUT])
       elif i == 3 or i == 4:
         # time out:
-        print 'Timeout when closing the ssh tunnel!'
+        print 'Exit from Device.'
         break
       else:
-        print 'Error'
         break
 
   def __del__(self):
-    self.tempFile.Close()
-    self.p_ssh.Close()
+    self.tmp_file.close()
+    #self.Close()
 
 # End of SSH class
 
diff --git a/sshto b/sshto
new file mode 100755
index 0000000..8c4f7e2
--- /dev/null
+++ b/sshto
@@ -0,0 +1,44 @@
+#!/bin/sh
+#
+# ssh (or, if no network, portsh) into the given device.
+#
+mydir=$(dirname "$0")
+cd "$mydir"
+
+name=$1
+shift
+
+log()
+{
+  echo "$@" >&2
+}
+
+
+die()
+{
+  log "$@"
+  exit 99
+}
+
+
+if [ -z "$name" ]; then
+  die "Usage: $0 <configname> [command...]"
+fi
+
+cmd=$(args="$*" ./run --quiet --raw "$name" -- '
+  if [ -n "$ip6" ] && ping6 -n -c1 -w2 $ip6 >/dev/null 2>&1; then
+    echo "ssh root@$ip6"
+  elif [ -n "$ip" ] && ping -n -c1 -w2 $ip >/dev/null 2>&1; then
+    echo "ssh root@$ip"
+  elif [ -n "$serialport" ]; then
+    if [ -n "$args" ]; then
+      echo "./portsh/portsh -pgoogle $serialport"
+    else
+      echo "./portsh/port $serialport"
+    fi
+  else
+    exit 1
+  fi
+') || die "Fatal: host '$name' has no live IP or serial port"
+log ">>> $cmd --" "$@"
+exec $cmd -- "$@"
diff --git a/testCase.py b/testCase.py
old mode 100644
new mode 100755
index 81e289d..bd2932e
--- a/testCase.py
+++ b/testCase.py
@@ -11,9 +11,13 @@
 __author__ = 'Lehan Meng (lmeng@google.com)'
 
 import datetime
+import json
 import logging
+import os
 import re
 
+import device
+
 
 class TestCase(object):
   """Base class for all test cases.
@@ -28,12 +32,13 @@
   placed in here.
   """
 
-  def __init__(self, **kwargs):
+  def __init__(self, ssh=None, **kwargs):
     """Initiate a test class instance.
 
     Args:
+      ssh: ssh tunnel to device
       kwargs['testID']: The assigned test ID on TCM
-      kwargs['title']: Title (section number) of the test case
+      kwargs['title']: Title (keyword) of the test case
       kwargs['start_time']: Start time of the test case (for statistic purpose)
       kwargs['key_word']: the label of the test case
       kwargs['description']: description if required
@@ -48,15 +53,165 @@
                       'configFile': 'config.cfg'}
     self.params = {}
 
-    for s in ('testID', 'title', 'start_time', 'key_word',
-              'description', 'configFile'):
-      if s in kwargs:
-        self.test_info[s] = kwargs[s]
+    for s in kwargs:
+      self.test_info[s] = kwargs[s]
 
-    self.log = logging.Logging()
-    self.ParseConfig()
+    self.device = device.Device()
+
+    self.log = logging.Logging(testID=self.test_info['testID'])
+    self.ParseJSONConfigFile()
+    #self.ParseConfig()
+    if ssh:
+      self.p_ssh = ssh
+    else:
+      self.p_ssh = None
     self.Run()
 
+  @staticmethod
+  def FindTestCaseID(file_name):
+    """Parse the configuration file for test case.
+
+    Find test ids in the configuration file.
+    Args:
+      file_name: the configure file to parse
+    Returns:
+      return the test case ids that needs to run the test
+    """
+    test_id_list = []
+    cfg_file = open(file_name, 'r')
+    cfg_list = cfg_file.readlines()
+    for line in cfg_list:
+      s = line.strip().strip('\n')
+      m = re.match(r'testID\s*:\s*(\d*)\s*$', s)
+      if (m is not None) and not s.startswith('#'):
+        test_id_list.append(m.group(1))
+    cfg_file.close()
+    return test_id_list
+
+  @staticmethod
+  def FindTestCaseToExecute(file_name):
+    """Parse the configuration file for test case.
+
+    Find test ids in the configuration file.
+    Args:
+      file_name: the configure file to parse
+    Returns:
+      return the test case ids that needs to run the test
+    """
+    test_id_list = []
+    try:
+      cfg_file = open(file_name, 'r')
+    except IOError:
+      print 'Cannot open the input file, return'
+      return os.sys.exit(1)
+    data = json.load(cfg_file)
+    cfg_file.close()
+
+    for test_case in data.iterkeys():
+      for key in data[test_case].iterkeys():
+        if isinstance(data[test_case][key], list) and key == 'testID':
+          for i in range(0, len(data[test_case][key])):
+            test_id_list.append(data[test_case][key][i])
+    return test_id_list
+
+  def ConfigForThisTestCase(self, data):
+    """Match if the configuration data is for current test case.
+
+    Args:
+      data: configuration data
+    Returns:
+      True: if the data is for current test case
+      False: if otherwise
+    """
+    for key in data.iterkeys():
+      if key == 'testID':
+        if self.test_info['testID'] in data[key]:
+          return True
+        else:
+          return False
+    return False
+
+  def ParseJSONConfigFile(self):
+    """Parse the JSON format configuration file.
+
+    The sturcture of a JSON file is as follows:
+    File format: parameter_names: string_values (or lists of values)
+                 comment is defined in the comment tag (JSON does not support
+                 comment).
+     'Test_Case_Description' : {
+     'comment': ['comments'],
+     'testID': ['11843140', '111111', '222222'],
+     'user' : 'root',
+     'addr' : '192.168.1.4',
+     'addr_ipv6' : 'None',
+     'pwd' : 'google',
+     'bruno_prompt' : 'gfibertv#',
+     'title' : 'Download',
+     'expected_version_id': '716006',
+     'expected_version': 'bruno-koala-5',
+     'downgrade_version': 'bruno-koala-4',
+     'downgrade_version_id': '728001',
+     'acs_url' : 'https://gfiber-acs-staging.appspot.com/cwmp',
+     'jump_server' : 'jmp.googlefiber.net',
+     'athena_user' : 'your_uname',
+     'athena_pwd' : 'your_pwd'
+    }
+
+    Returns:
+      return True if configure file parsed successfully
+    """
+    try:
+      self.cfg_file = open(self.test_info['configFile'], 'r')
+    except IOError:
+      print 'Cannot open the input file, return'
+      return os.sys.exit(1)
+    data = json.load(self.cfg_file)
+    self.cfg_file.close()
+
+    test_id_matched = False
+    for test_case in data.iterkeys():
+      if not self.ConfigForThisTestCase(data[test_case]):
+        continue
+      for key in data[test_case].iterkeys():
+        if isinstance(data[test_case][key], (str, unicode)):
+          value = data[test_case][key]
+          if not value or value == 'None':
+            self.params[key] = None
+          else:
+            self.params[key] = value
+        elif isinstance(data[test_case][key], dict):
+          if (key == 'title' and
+              self.test_info['testID'] in data[test_case][key]):
+            self.params[key] = data[test_case][key][self.test_info['testID']]
+            self.test_info['key_word'] = self.params[key]
+          else:
+            self.params[key] = data[test_case][key]
+        elif isinstance(data[test_case][key], list):
+          if key == 'testID':
+            for i in range(0, len(data[test_case][key])):
+              test_id = data[test_case][key][i]
+              if str(self.test_info['testID']) == test_id:
+                test_id_matched = True
+          elif key == 'comment':
+            # do not process comment lines
+            continue
+          else:
+            self.params[key] = data[test_case][key]
+        else:
+          info = self.log.CreateErrorInfo(
+              'Low', 'Warning: Configuration Parameter type unknown '
+              'in file ' + self.test_info['configFile'] + ': '
+              + test_case + ':' + key)
+          self.log.SendLine(self.test_info, info)
+
+    if not test_id_matched:
+      info = self.log.CreateErrorInfo(
+          'Low', 'Warning: No configuration found for this test '
+          'case in file: ' + self.test_info['configFile'])
+      self.log.SendLine(self.test_info, info)
+      os.sys.exit(1)
+    return True
+
   def ParseConfig(self):
     """Parse the configuration file for test case.
 
@@ -82,13 +237,16 @@
       reuturn 1 if succeed
     """
     # dictionary for params
-    self.cfg_file = open(self.test_info['configFile'], 'r')
+    try:
+      self.cfg_file = open(self.test_info['configFile'], 'r')
+    except IOError:
+      print 'Cannot open the input file, return'
+      return os.sys.exit(1)
 
     cfg_list = self.cfg_file.readlines()
     test_id_matched = False
-
     for line in cfg_list:
-      s = line.lstrip().strip().strip('\n')
+      s = line.strip().strip('\n')
       m = re.match('testID: ' + str(self.test_info['testID']), s)
       if (m is not None) and not s.startswith('#'):
         # test case matching succeed
@@ -105,7 +263,7 @@
           self.log.sendLine(self.test_info, info)
 
         s = cfg_list[index]
-        s = s.lstrip().rstrip()
+        s = s.strip()
         s = s.split('#', 2)[0]
 
         m = re.match('testID: ', s)
@@ -114,8 +272,8 @@
           # read and create current test case configuration data as dictionary
           # format: parameter = value
           if s is not '' and not s.startswith('#'):
-            name = s.split('=')[0].strip().lstrip()
-            value = s.split('=')[1].strip().lstrip()
+            name = s.split('=')[0].strip()
+            value = s.split('=')[1].strip()
             if not value or value == 'None':
               self.params[name] = None
             else:
@@ -124,17 +282,18 @@
           index += 1
           if index == len(cfg_list): break
           s = cfg_list[index]
-          s = s.lstrip().rstrip()
+          s = s.strip()
           m = re.match('testID: ', s)
 
     # all configuration parsed
     self.cfg_file.close()
     if not test_id_matched:
       # end of file  reached
-      info = self.log.createErrorInfo(
+      info = self.log.CreateErrorInfo(
           'Low', 'Warning: No configuration found for this test case in file: '
           + self.test_info['configFile'])
-      self.log.sendLine(self.test_info, info)
+      self.log.SendLine(self.test_info, info)
+      os.sys.exit(1)
     return True
 
   def AddConfigParams(self, par='par', value='value'):
@@ -144,7 +303,8 @@
       par: parameter name
       value: parameter value
     """
-    self.params.setdefault(par.strip().lstrip(), value.strip().lstrip())
+    if par and value:
+      self.params[par.strip()] = value.strip()
 
   def __del__(self):
     pass
@@ -159,4 +319,4 @@
     ##### Add your code here -- END   #######
 
   def Destructor(self):
-    pass
\ No newline at end of file
+    pass