diff --git a/Quali/lanforge_resource/.gitignore b/Quali/lanforge_resource/.gitignore
new file mode 100644
index 00000000..1e1a44c7
--- /dev/null
+++ b/Quali/lanforge_resource/.gitignore
@@ -0,0 +1,60 @@
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+env/
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+*.egg-info/
+.installed.cfg
+*.egg
+
+# PyInstaller
+#  Usually these files are written by a python script from a template
+#  before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*,cover
+.hypothesis/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+cloudshell_config.yml
diff --git a/Quali/lanforge_resource/TOSCA-Metadata/TOSCA.meta b/Quali/lanforge_resource/TOSCA-Metadata/TOSCA.meta
new file mode 100644
index 00000000..5a9b442f
--- /dev/null
+++ b/Quali/lanforge_resource/TOSCA-Metadata/TOSCA.meta
@@ -0,0 +1,4 @@
+TOSCA-Meta-File-Version: 1.0
+CSAR-Version: 0.1.0
+Created-By: Anonymous
+Entry-Definitions: shell-definition.yaml
\ No newline at end of file
diff --git a/Quali/lanforge_resource/canvil2-64x64-gray-yel-ico.png b/Quali/lanforge_resource/canvil2-64x64-gray-yel-ico.png
new file mode 100644
index 00000000..9abca242
Binary files /dev/null and b/Quali/lanforge_resource/canvil2-64x64-gray-yel-ico.png differ
diff --git a/Quali/lanforge_resource/deployment.xml b/Quali/lanforge_resource/deployment.xml
new file mode 100644
index 00000000..fa1f6a32
--- /dev/null
+++ b/Quali/lanforge_resource/deployment.xml
@@ -0,0 +1,53 @@
+
+
+
+    
+    localhost
+
+    
+    8029
+
+    
+    YOUR_USERNAME
+    YOUR_PASSWORD
+    Global
+
+    
+    dont_upload_me.xml
+
+    
+    
+       
+       
+       
+        
+            
+            
+                 src
+            
+            
+            LanforgeResourceDriver
+        
+    
+
+    
+    
+    
+
\ No newline at end of file
diff --git a/Quali/lanforge_resource/docs/readme.rst b/Quali/lanforge_resource/docs/readme.rst
new file mode 100644
index 00000000..4dc0eb5d
--- /dev/null
+++ b/Quali/lanforge_resource/docs/readme.rst
@@ -0,0 +1,3 @@
+.. _readme:
+
+.. include:: ../README.rst
diff --git a/Quali/lanforge_resource/shell-definition.yaml b/Quali/lanforge_resource/shell-definition.yaml
new file mode 100644
index 00000000..2a0f9cbb
--- /dev/null
+++ b/Quali/lanforge_resource/shell-definition.yaml
@@ -0,0 +1,45 @@
+tosca_definitions_version: tosca_simple_yaml_1_0
+
+metadata:
+  template_name: Lanforge Resource
+  template_author: Anonymous
+  template_version: 0.1.0
+  template_icon: shell-icon.png
+
+description: >
+  TOSCA based resource shell
+
+imports:
+  - cloudshell_standard: cloudshell_resource_standard_2_0_3.yaml
+
+node_types:
+
+  vendor.resource.Lanforge Resource:
+    derived_from: cloudshell.nodes.GenericResource
+    #properties:
+    #  my_property:
+    #    type: string          # optional values: string, integer, float, boolean, cloudshell.datatypes.Password
+    #    default: fast
+    #    description: Some attribute description
+    #    constraints:
+    #      - valid_values: [fast, slow]
+    capabilities:
+      auto_discovery_capability:
+        type: cloudshell.capabilities.AutoDiscovery
+        properties:        
+          enable_auto_discovery:
+            type: boolean
+            default: true
+          auto_discovery_description:
+            type: string
+            default: Describe the auto discovery
+          inventory_description:
+            type: string
+            default: Describe the resource shell template
+    artifacts:
+      icon:
+        file: canvil2-64x64-gray-yel-ico.png
+        type: tosca.artifacts.File
+      driver:
+        file: LanforgeResourceDriver.zip
+        type: tosca.artifacts.File
diff --git a/Quali/lanforge_resource/shell-icon.png b/Quali/lanforge_resource/shell-icon.png
new file mode 100644
index 00000000..820b28fd
Binary files /dev/null and b/Quali/lanforge_resource/shell-icon.png differ
diff --git a/Quali/lanforge_resource/src/data_model.py b/Quali/lanforge_resource/src/data_model.py
new file mode 100644
index 00000000..329bdfbb
--- /dev/null
+++ b/Quali/lanforge_resource/src/data_model.py
@@ -0,0 +1,1029 @@
+from cloudshell.shell.core.driver_context import ResourceCommandContext, AutoLoadDetails, AutoLoadAttribute, \
+    AutoLoadResource
+from collections import defaultdict
+
+
+class LegacyUtils(object):
+    def __init__(self):
+        self._datamodel_clss_dict = self.__generate_datamodel_classes_dict()
+
+    def migrate_autoload_details(self, autoload_details, context):
+        model_name = context.resource.model
+        root_name = context.resource.name
+        root = self.__create_resource_from_datamodel(model_name, root_name)
+        attributes = self.__create_attributes_dict(autoload_details.attributes)
+        self.__attach_attributes_to_resource(attributes, '', root)
+        self.__build_sub_resoruces_hierarchy(root, autoload_details.resources, attributes)
+        return root
+
+    def __create_resource_from_datamodel(self, model_name, res_name):
+        return self._datamodel_clss_dict[model_name](res_name)
+
+    def __create_attributes_dict(self, attributes_lst):
+        d = defaultdict(list)
+        for attribute in attributes_lst:
+            d[attribute.relative_address].append(attribute)
+        return d
+
+    def __build_sub_resoruces_hierarchy(self, root, sub_resources, attributes):
+        d = defaultdict(list)
+        for resource in sub_resources:
+            splitted = resource.relative_address.split('/')
+            parent = '' if len(splitted) == 1 else resource.relative_address.rsplit('/', 1)[0]
+            rank = len(splitted)
+            d[rank].append((parent, resource))
+
+        self.__set_models_hierarchy_recursively(d, 1, root, '', attributes)
+
+    def __set_models_hierarchy_recursively(self, dict, rank, manipulated_resource, resource_relative_addr, attributes):
+        if rank not in dict: # validate if key exists
+            pass
+
+        for (parent, resource) in dict[rank]:
+            if parent == resource_relative_addr:
+                sub_resource = self.__create_resource_from_datamodel(
+                    resource.model.replace(' ', ''),
+                    resource.name)
+                self.__attach_attributes_to_resource(attributes, resource.relative_address, sub_resource)
+                manipulated_resource.add_sub_resource(
+                    self.__slice_parent_from_relative_path(parent, resource.relative_address), sub_resource)
+                self.__set_models_hierarchy_recursively(
+                    dict,
+                    rank + 1,
+                    sub_resource,
+                    resource.relative_address,
+                    attributes)
+
+    def __attach_attributes_to_resource(self, attributes, curr_relative_addr, resource):
+        for attribute in attributes[curr_relative_addr]:
+            setattr(resource, attribute.attribute_name.lower().replace(' ', '_'), attribute.attribute_value)
+        del attributes[curr_relative_addr]
+
+    def __slice_parent_from_relative_path(self, parent, relative_addr):
+        if parent is '':
+            return relative_addr
+        return relative_addr[len(parent) + 1:] # + 1 because we want to remove the seperator also
+
+    def __generate_datamodel_classes_dict(self):
+        return dict(self.__collect_generated_classes())
+
+    def __collect_generated_classes(self):
+        import sys, inspect
+        return inspect.getmembers(sys.modules[__name__], inspect.isclass)
+
+
+class LanforgeResource(object):
+    def __init__(self, name):
+        """
+        
+        """
+        self.attributes = {}
+        self.resources = {}
+        self._cloudshell_model_name = 'Lanforge Resource'
+        self._name = name
+
+    def add_sub_resource(self, relative_path, sub_resource):
+        self.resources[relative_path] = sub_resource
+
+    @classmethod
+    def create_from_context(cls, context):
+        """
+        Creates an instance of NXOS by given context
+        :param context: cloudshell.shell.core.driver_context.ResourceCommandContext
+        :type context: cloudshell.shell.core.driver_context.ResourceCommandContext
+        :return:
+        :rtype LanforgeResource
+        """
+        result = LanforgeResource(name=context.resource.name)
+        for attr in context.resource.attributes:
+            result.attributes[attr] = context.resource.attributes[attr]
+        return result
+
+    def create_autoload_details(self, relative_path=''):
+        """
+        :param relative_path:
+        :type relative_path: str
+        :return
+        """
+        resources = [AutoLoadResource(model=self.resources[r].cloudshell_model_name,
+            name=self.resources[r].name,
+            relative_address=self._get_relative_path(r, relative_path))
+            for r in self.resources]
+        attributes = [AutoLoadAttribute(relative_path, a, self.attributes[a]) for a in self.attributes]
+        autoload_details = AutoLoadDetails(resources, attributes)
+        for r in self.resources:
+            curr_path = relative_path + '/' + r if relative_path else r
+            curr_auto_load_details = self.resources[r].create_autoload_details(curr_path)
+            autoload_details = self._merge_autoload_details(autoload_details, curr_auto_load_details)
+        return autoload_details
+
+    def _get_relative_path(self, child_path, parent_path):
+        """
+        Combines relative path
+        :param child_path: Path of a model within it parent model, i.e 1
+        :type child_path: str
+        :param parent_path: Full path of parent model, i.e 1/1. Might be empty for root model
+        :type parent_path: str
+        :return: Combined path
+        :rtype str
+        """
+        return parent_path + '/' + child_path if parent_path else child_path
+
+    @staticmethod
+    def _merge_autoload_details(autoload_details1, autoload_details2):
+        """
+        Merges two instances of AutoLoadDetails into the first one
+        :param autoload_details1:
+        :type autoload_details1: AutoLoadDetails
+        :param autoload_details2:
+        :type autoload_details2: AutoLoadDetails
+        :return:
+        :rtype AutoLoadDetails
+        """
+        for attribute in autoload_details2.attributes:
+            autoload_details1.attributes.append(attribute)
+        for resource in autoload_details2.resources:
+            autoload_details1.resources.append(resource)
+        return autoload_details1
+
+    @property
+    def cloudshell_model_name(self):
+        """
+        Returns the name of the Cloudshell model
+        :return:
+        """
+        return 'LanforgeResource'
+
+    @property
+    def user(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.User'] if 'Lanforge Resource.User' in self.attributes else None
+
+    @user.setter
+    def user(self, value):
+        """
+        User with administrative privileges
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.User'] = value
+
+    @property
+    def password(self):
+        """
+        :rtype: string
+        """
+        return self.attributes['Lanforge Resource.Password'] if 'Lanforge Resource.Password' in self.attributes else None
+
+    @password.setter
+    def password(self, value):
+        """
+        
+        :type value: string
+        """
+        self.attributes['Lanforge Resource.Password'] = value
+
+    @property
+    def enable_password(self):
+        """
+        :rtype: string
+        """
+        return self.attributes['Lanforge Resource.Enable Password'] if 'Lanforge Resource.Enable Password' in self.attributes else None
+
+    @enable_password.setter
+    def enable_password(self, value):
+        """
+        The enable password is required by some CLI protocols such as Telnet and is required according to the device configuration.
+        :type value: string
+        """
+        self.attributes['Lanforge Resource.Enable Password'] = value
+
+    @property
+    def power_management(self):
+        """
+        :rtype: bool
+        """
+        return self.attributes['Lanforge Resource.Power Management'] if 'Lanforge Resource.Power Management' in self.attributes else None
+
+    @power_management.setter
+    def power_management(self, value=True):
+        """
+        Used by the power management orchestration, if enabled, to determine whether to automatically manage the device power status. Enabled by default.
+        :type value: bool
+        """
+        self.attributes['Lanforge Resource.Power Management'] = value
+
+    @property
+    def sessions_concurrency_limit(self):
+        """
+        :rtype: float
+        """
+        return self.attributes['Lanforge Resource.Sessions Concurrency Limit'] if 'Lanforge Resource.Sessions Concurrency Limit' in self.attributes else None
+
+    @sessions_concurrency_limit.setter
+    def sessions_concurrency_limit(self, value='1'):
+        """
+        The maximum number of concurrent sessions that the driver will open to the device. Default is 1 (no concurrency).
+        :type value: float
+        """
+        self.attributes['Lanforge Resource.Sessions Concurrency Limit'] = value
+
+    @property
+    def snmp_read_community(self):
+        """
+        :rtype: string
+        """
+        return self.attributes['Lanforge Resource.SNMP Read Community'] if 'Lanforge Resource.SNMP Read Community' in self.attributes else None
+
+    @snmp_read_community.setter
+    def snmp_read_community(self, value):
+        """
+        The SNMP Read-Only Community String is like a password. It is sent along with each SNMP Get-Request and allows (or denies) access to device.
+        :type value: string
+        """
+        self.attributes['Lanforge Resource.SNMP Read Community'] = value
+
+    @property
+    def snmp_write_community(self):
+        """
+        :rtype: string
+        """
+        return self.attributes['Lanforge Resource.SNMP Write Community'] if 'Lanforge Resource.SNMP Write Community' in self.attributes else None
+
+    @snmp_write_community.setter
+    def snmp_write_community(self, value):
+        """
+        The SNMP Write Community String is like a password. It is sent along with each SNMP Set-Request and allows (or denies) chaning MIBs values.
+        :type value: string
+        """
+        self.attributes['Lanforge Resource.SNMP Write Community'] = value
+
+    @property
+    def snmp_v3_user(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.SNMP V3 User'] if 'Lanforge Resource.SNMP V3 User' in self.attributes else None
+
+    @snmp_v3_user.setter
+    def snmp_v3_user(self, value):
+        """
+        Relevant only in case SNMP V3 is in use.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.SNMP V3 User'] = value
+
+    @property
+    def snmp_v3_password(self):
+        """
+        :rtype: string
+        """
+        return self.attributes['Lanforge Resource.SNMP V3 Password'] if 'Lanforge Resource.SNMP V3 Password' in self.attributes else None
+
+    @snmp_v3_password.setter
+    def snmp_v3_password(self, value):
+        """
+        Relevant only in case SNMP V3 is in use.
+        :type value: string
+        """
+        self.attributes['Lanforge Resource.SNMP V3 Password'] = value
+
+    @property
+    def snmp_v3_private_key(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.SNMP V3 Private Key'] if 'Lanforge Resource.SNMP V3 Private Key' in self.attributes else None
+
+    @snmp_v3_private_key.setter
+    def snmp_v3_private_key(self, value):
+        """
+        Relevant only in case SNMP V3 is in use.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.SNMP V3 Private Key'] = value
+
+    @property
+    def snmp_v3_authentication_protocol(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.SNMP V3 Authentication Protocol'] if 'Lanforge Resource.SNMP V3 Authentication Protocol' in self.attributes else None
+
+    @snmp_v3_authentication_protocol.setter
+    def snmp_v3_authentication_protocol(self, value='No Authentication Protocol'):
+        """
+        Relevant only in case SNMP V3 is in use.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.SNMP V3 Authentication Protocol'] = value
+
+    @property
+    def snmp_v3_privacy_protocol(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.SNMP V3 Privacy Protocol'] if 'Lanforge Resource.SNMP V3 Privacy Protocol' in self.attributes else None
+
+    @snmp_v3_privacy_protocol.setter
+    def snmp_v3_privacy_protocol(self, value='No Privacy Protocol'):
+        """
+        Relevant only in case SNMP V3 is in use.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.SNMP V3 Privacy Protocol'] = value
+
+    @property
+    def snmp_version(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.SNMP Version'] if 'Lanforge Resource.SNMP Version' in self.attributes else None
+
+    @snmp_version.setter
+    def snmp_version(self, value=''):
+        """
+        The version of SNMP to use. Possible values are v1, v2c and v3.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.SNMP Version'] = value
+
+    @property
+    def enable_snmp(self):
+        """
+        :rtype: bool
+        """
+        return self.attributes['Lanforge Resource.Enable SNMP'] if 'Lanforge Resource.Enable SNMP' in self.attributes else None
+
+    @enable_snmp.setter
+    def enable_snmp(self, value=True):
+        """
+        If set to True and SNMP isn???t enabled yet in the device the Shell will automatically enable SNMP in the device when Autoload command is called. SNMP must be enabled on the device for the Autoload command to run successfully. True by default.
+        :type value: bool
+        """
+        self.attributes['Lanforge Resource.Enable SNMP'] = value
+
+    @property
+    def disable_snmp(self):
+        """
+        :rtype: bool
+        """
+        return self.attributes['Lanforge Resource.Disable SNMP'] if 'Lanforge Resource.Disable SNMP' in self.attributes else None
+
+    @disable_snmp.setter
+    def disable_snmp(self, value=False):
+        """
+        If set to True SNMP will be disabled automatically by the Shell after the Autoload command execution is completed. False by default.
+        :type value: bool
+        """
+        self.attributes['Lanforge Resource.Disable SNMP'] = value
+
+    @property
+    def console_server_ip_address(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.Console Server IP Address'] if 'Lanforge Resource.Console Server IP Address' in self.attributes else None
+
+    @console_server_ip_address.setter
+    def console_server_ip_address(self, value):
+        """
+        The IP address of the console server, in IPv4 format.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.Console Server IP Address'] = value
+
+    @property
+    def console_user(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.Console User'] if 'Lanforge Resource.Console User' in self.attributes else None
+
+    @console_user.setter
+    def console_user(self, value):
+        """
+        
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.Console User'] = value
+
+    @property
+    def console_port(self):
+        """
+        :rtype: float
+        """
+        return self.attributes['Lanforge Resource.Console Port'] if 'Lanforge Resource.Console Port' in self.attributes else None
+
+    @console_port.setter
+    def console_port(self, value):
+        """
+        The port on the console server, usually TCP port, which the device is associated with.
+        :type value: float
+        """
+        self.attributes['Lanforge Resource.Console Port'] = value
+
+    @property
+    def console_password(self):
+        """
+        :rtype: string
+        """
+        return self.attributes['Lanforge Resource.Console Password'] if 'Lanforge Resource.Console Password' in self.attributes else None
+
+    @console_password.setter
+    def console_password(self, value):
+        """
+        
+        :type value: string
+        """
+        self.attributes['Lanforge Resource.Console Password'] = value
+
+    @property
+    def cli_connection_type(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.CLI Connection Type'] if 'Lanforge Resource.CLI Connection Type' in self.attributes else None
+
+    @cli_connection_type.setter
+    def cli_connection_type(self, value='Auto'):
+        """
+        The CLI connection type that will be used by the driver. Possible values are Auto, Console, SSH, Telnet and TCP. If Auto is selected the driver will choose the available connection type automatically. Default value is Auto.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.CLI Connection Type'] = value
+
+    @property
+    def cli_tcp_port(self):
+        """
+        :rtype: float
+        """
+        return self.attributes['Lanforge Resource.CLI TCP Port'] if 'Lanforge Resource.CLI TCP Port' in self.attributes else None
+
+    @cli_tcp_port.setter
+    def cli_tcp_port(self, value):
+        """
+        TCP Port to user for CLI connection. If kept empty a default CLI port will be used based on the chosen protocol, for example Telnet will use port 23.
+        :type value: float
+        """
+        self.attributes['Lanforge Resource.CLI TCP Port'] = value
+
+    @property
+    def backup_location(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.Backup Location'] if 'Lanforge Resource.Backup Location' in self.attributes else None
+
+    @backup_location.setter
+    def backup_location(self, value):
+        """
+        Used by the save/restore orchestration to determine where backups should be saved.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.Backup Location'] = value
+
+    @property
+    def backup_type(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.Backup Type'] if 'Lanforge Resource.Backup Type' in self.attributes else None
+
+    @backup_type.setter
+    def backup_type(self, value='File System'):
+        """
+        Supported protocols for saving and restoring of configuration and firmware files. Possible values are 'File System' 'FTP' and 'TFTP'. Default value is 'File System'.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.Backup Type'] = value
+
+    @property
+    def backup_user(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.Backup User'] if 'Lanforge Resource.Backup User' in self.attributes else None
+
+    @backup_user.setter
+    def backup_user(self, value):
+        """
+        Username for the storage server used for saving and restoring of configuration and firmware files.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.Backup User'] = value
+
+    @property
+    def backup_password(self):
+        """
+        :rtype: string
+        """
+        return self.attributes['Lanforge Resource.Backup Password'] if 'Lanforge Resource.Backup Password' in self.attributes else None
+
+    @backup_password.setter
+    def backup_password(self, value):
+        """
+        Password for the storage server used for saving and restoring of configuration and firmware files.
+        :type value: string
+        """
+        self.attributes['Lanforge Resource.Backup Password'] = value
+
+    @property
+    def name(self):
+        """
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, value):
+        """
+        
+        :type value: str
+        """
+        self._name = value
+
+    @property
+    def cloudshell_model_name(self):
+        """
+        :rtype: str
+        """
+        return self._cloudshell_model_name
+
+    @cloudshell_model_name.setter
+    def cloudshell_model_name(self, value):
+        """
+        
+        :type value: str
+        """
+        self._cloudshell_model_name = value
+
+    @property
+    def system_name(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['CS_GenericResource.System Name'] if 'CS_GenericResource.System Name' in self.attributes else None
+
+    @system_name.setter
+    def system_name(self, value):
+        """
+        A unique identifier for the device, if exists in the device terminal/os.
+        :type value: str
+        """
+        self.attributes['CS_GenericResource.System Name'] = value
+
+    @property
+    def vendor(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['CS_GenericResource.Vendor'] if 'CS_GenericResource.Vendor' in self.attributes else None
+
+    @vendor.setter
+    def vendor(self, value=''):
+        """
+        The name of the device manufacture.
+        :type value: str
+        """
+        self.attributes['CS_GenericResource.Vendor'] = value
+
+    @property
+    def contact_name(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['CS_GenericResource.Contact Name'] if 'CS_GenericResource.Contact Name' in self.attributes else None
+
+    @contact_name.setter
+    def contact_name(self, value):
+        """
+        The name of a contact registered in the device.
+        :type value: str
+        """
+        self.attributes['CS_GenericResource.Contact Name'] = value
+
+    @property
+    def location(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['CS_GenericResource.Location'] if 'CS_GenericResource.Location' in self.attributes else None
+
+    @location.setter
+    def location(self, value=''):
+        """
+        The device physical location identifier. For example Lab1/Floor2/Row5/Slot4.
+        :type value: str
+        """
+        self.attributes['CS_GenericResource.Location'] = value
+
+    @property
+    def model(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['CS_GenericResource.Model'] if 'CS_GenericResource.Model' in self.attributes else None
+
+    @model.setter
+    def model(self, value=''):
+        """
+        The device model. This information is typically used for abstract resource filtering.
+        :type value: str
+        """
+        self.attributes['CS_GenericResource.Model'] = value
+
+    @property
+    def model_name(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['CS_GenericResource.Model Name'] if 'CS_GenericResource.Model Name' in self.attributes else None
+
+    @model_name.setter
+    def model_name(self, value=''):
+        """
+        The catalog name of the device model. This attribute will be displayed in CloudShell instead of the CloudShell model.
+        :type value: str
+        """
+        self.attributes['CS_GenericResource.Model Name'] = value
+
+
+class ResourcePort(object):
+    def __init__(self, name):
+        """
+        
+        """
+        self.attributes = {}
+        self.resources = {}
+        self._cloudshell_model_name = 'Lanforge Resource.ResourcePort'
+        self._name = name
+
+    def add_sub_resource(self, relative_path, sub_resource):
+        self.resources[relative_path] = sub_resource
+
+    @classmethod
+    def create_from_context(cls, context):
+        """
+        Creates an instance of NXOS by given context
+        :param context: cloudshell.shell.core.driver_context.ResourceCommandContext
+        :type context: cloudshell.shell.core.driver_context.ResourceCommandContext
+        :return:
+        :rtype ResourcePort
+        """
+        result = ResourcePort(name=context.resource.name)
+        for attr in context.resource.attributes:
+            result.attributes[attr] = context.resource.attributes[attr]
+        return result
+
+    def create_autoload_details(self, relative_path=''):
+        """
+        :param relative_path:
+        :type relative_path: str
+        :return
+        """
+        resources = [AutoLoadResource(model=self.resources[r].cloudshell_model_name,
+            name=self.resources[r].name,
+            relative_address=self._get_relative_path(r, relative_path))
+            for r in self.resources]
+        attributes = [AutoLoadAttribute(relative_path, a, self.attributes[a]) for a in self.attributes]
+        autoload_details = AutoLoadDetails(resources, attributes)
+        for r in self.resources:
+            curr_path = relative_path + '/' + r if relative_path else r
+            curr_auto_load_details = self.resources[r].create_autoload_details(curr_path)
+            autoload_details = self._merge_autoload_details(autoload_details, curr_auto_load_details)
+        return autoload_details
+
+    def _get_relative_path(self, child_path, parent_path):
+        """
+        Combines relative path
+        :param child_path: Path of a model within it parent model, i.e 1
+        :type child_path: str
+        :param parent_path: Full path of parent model, i.e 1/1. Might be empty for root model
+        :type parent_path: str
+        :return: Combined path
+        :rtype str
+        """
+        return parent_path + '/' + child_path if parent_path else child_path
+
+    @staticmethod
+    def _merge_autoload_details(autoload_details1, autoload_details2):
+        """
+        Merges two instances of AutoLoadDetails into the first one
+        :param autoload_details1:
+        :type autoload_details1: AutoLoadDetails
+        :param autoload_details2:
+        :type autoload_details2: AutoLoadDetails
+        :return:
+        :rtype AutoLoadDetails
+        """
+        for attribute in autoload_details2.attributes:
+            autoload_details1.attributes.append(attribute)
+        for resource in autoload_details2.resources:
+            autoload_details1.resources.append(resource)
+        return autoload_details1
+
+    @property
+    def cloudshell_model_name(self):
+        """
+        Returns the name of the Cloudshell model
+        :return:
+        """
+        return 'ResourcePort'
+
+    @property
+    def mac_address(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.ResourcePort.MAC Address'] if 'Lanforge Resource.ResourcePort.MAC Address' in self.attributes else None
+
+    @mac_address.setter
+    def mac_address(self, value=''):
+        """
+        
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.ResourcePort.MAC Address'] = value
+
+    @property
+    def ipv4_address(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.ResourcePort.IPv4 Address'] if 'Lanforge Resource.ResourcePort.IPv4 Address' in self.attributes else None
+
+    @ipv4_address.setter
+    def ipv4_address(self, value):
+        """
+        
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.ResourcePort.IPv4 Address'] = value
+
+    @property
+    def ipv6_address(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.ResourcePort.IPv6 Address'] if 'Lanforge Resource.ResourcePort.IPv6 Address' in self.attributes else None
+
+    @ipv6_address.setter
+    def ipv6_address(self, value):
+        """
+        
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.ResourcePort.IPv6 Address'] = value
+
+    @property
+    def port_speed(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.ResourcePort.Port Speed'] if 'Lanforge Resource.ResourcePort.Port Speed' in self.attributes else None
+
+    @port_speed.setter
+    def port_speed(self, value):
+        """
+        The port speed (e.g 10Gb/s, 40Gb/s, 100Mb/s)
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.ResourcePort.Port Speed'] = value
+
+    @property
+    def name(self):
+        """
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, value):
+        """
+        
+        :type value: str
+        """
+        self._name = value
+
+    @property
+    def cloudshell_model_name(self):
+        """
+        :rtype: str
+        """
+        return self._cloudshell_model_name
+
+    @cloudshell_model_name.setter
+    def cloudshell_model_name(self, value):
+        """
+        
+        :type value: str
+        """
+        self._cloudshell_model_name = value
+
+    @property
+    def model_name(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['CS_Port.Model Name'] if 'CS_Port.Model Name' in self.attributes else None
+
+    @model_name.setter
+    def model_name(self, value=''):
+        """
+        The catalog name of the device model. This attribute will be displayed in CloudShell instead of the CloudShell model.
+        :type value: str
+        """
+        self.attributes['CS_Port.Model Name'] = value
+
+
+class GenericPowerPort(object):
+    def __init__(self, name):
+        """
+        
+        """
+        self.attributes = {}
+        self.resources = {}
+        self._cloudshell_model_name = 'Lanforge Resource.GenericPowerPort'
+        self._name = name
+
+    def add_sub_resource(self, relative_path, sub_resource):
+        self.resources[relative_path] = sub_resource
+
+    @classmethod
+    def create_from_context(cls, context):
+        """
+        Creates an instance of NXOS by given context
+        :param context: cloudshell.shell.core.driver_context.ResourceCommandContext
+        :type context: cloudshell.shell.core.driver_context.ResourceCommandContext
+        :return:
+        :rtype GenericPowerPort
+        """
+        result = GenericPowerPort(name=context.resource.name)
+        for attr in context.resource.attributes:
+            result.attributes[attr] = context.resource.attributes[attr]
+        return result
+
+    def create_autoload_details(self, relative_path=''):
+        """
+        :param relative_path:
+        :type relative_path: str
+        :return
+        """
+        resources = [AutoLoadResource(model=self.resources[r].cloudshell_model_name,
+            name=self.resources[r].name,
+            relative_address=self._get_relative_path(r, relative_path))
+            for r in self.resources]
+        attributes = [AutoLoadAttribute(relative_path, a, self.attributes[a]) for a in self.attributes]
+        autoload_details = AutoLoadDetails(resources, attributes)
+        for r in self.resources:
+            curr_path = relative_path + '/' + r if relative_path else r
+            curr_auto_load_details = self.resources[r].create_autoload_details(curr_path)
+            autoload_details = self._merge_autoload_details(autoload_details, curr_auto_load_details)
+        return autoload_details
+
+    def _get_relative_path(self, child_path, parent_path):
+        """
+        Combines relative path
+        :param child_path: Path of a model within it parent model, i.e 1
+        :type child_path: str
+        :param parent_path: Full path of parent model, i.e 1/1. Might be empty for root model
+        :type parent_path: str
+        :return: Combined path
+        :rtype str
+        """
+        return parent_path + '/' + child_path if parent_path else child_path
+
+    @staticmethod
+    def _merge_autoload_details(autoload_details1, autoload_details2):
+        """
+        Merges two instances of AutoLoadDetails into the first one
+        :param autoload_details1:
+        :type autoload_details1: AutoLoadDetails
+        :param autoload_details2:
+        :type autoload_details2: AutoLoadDetails
+        :return:
+        :rtype AutoLoadDetails
+        """
+        for attribute in autoload_details2.attributes:
+            autoload_details1.attributes.append(attribute)
+        for resource in autoload_details2.resources:
+            autoload_details1.resources.append(resource)
+        return autoload_details1
+
+    @property
+    def cloudshell_model_name(self):
+        """
+        Returns the name of the Cloudshell model
+        :return:
+        """
+        return 'GenericPowerPort'
+
+    @property
+    def model(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.GenericPowerPort.Model'] if 'Lanforge Resource.GenericPowerPort.Model' in self.attributes else None
+
+    @model.setter
+    def model(self, value):
+        """
+        The device model. This information is typically used for abstract resource filtering.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.GenericPowerPort.Model'] = value
+
+    @property
+    def serial_number(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.GenericPowerPort.Serial Number'] if 'Lanforge Resource.GenericPowerPort.Serial Number' in self.attributes else None
+
+    @serial_number.setter
+    def serial_number(self, value):
+        """
+        
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.GenericPowerPort.Serial Number'] = value
+
+    @property
+    def version(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.GenericPowerPort.Version'] if 'Lanforge Resource.GenericPowerPort.Version' in self.attributes else None
+
+    @version.setter
+    def version(self, value):
+        """
+        The firmware version of the resource.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.GenericPowerPort.Version'] = value
+
+    @property
+    def port_description(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['Lanforge Resource.GenericPowerPort.Port Description'] if 'Lanforge Resource.GenericPowerPort.Port Description' in self.attributes else None
+
+    @port_description.setter
+    def port_description(self, value):
+        """
+        The description of the port as configured in the device.
+        :type value: str
+        """
+        self.attributes['Lanforge Resource.GenericPowerPort.Port Description'] = value
+
+    @property
+    def name(self):
+        """
+        :rtype: str
+        """
+        return self._name
+
+    @name.setter
+    def name(self, value):
+        """
+        
+        :type value: str
+        """
+        self._name = value
+
+    @property
+    def cloudshell_model_name(self):
+        """
+        :rtype: str
+        """
+        return self._cloudshell_model_name
+
+    @cloudshell_model_name.setter
+    def cloudshell_model_name(self, value):
+        """
+        
+        :type value: str
+        """
+        self._cloudshell_model_name = value
+
+    @property
+    def model_name(self):
+        """
+        :rtype: str
+        """
+        return self.attributes['CS_PowerPort.Model Name'] if 'CS_PowerPort.Model Name' in self.attributes else None
+
+    @model_name.setter
+    def model_name(self, value=''):
+        """
+        The catalog name of the device model. This attribute will be displayed in CloudShell instead of the CloudShell model.
+        :type value: str
+        """
+        self.attributes['CS_PowerPort.Model Name'] = value
+
+
+
diff --git a/Quali/lanforge_resource/src/driver.py b/Quali/lanforge_resource/src/driver.py
new file mode 100755
index 00000000..277dc073
--- /dev/null
+++ b/Quali/lanforge_resource/src/driver.py
@@ -0,0 +1,309 @@
+from cloudshell.shell.core.resource_driver_interface import ResourceDriverInterface
+from cloudshell.shell.core.driver_context import InitCommandContext, ResourceCommandContext, AutoLoadResource, \
+    AutoLoadAttribute, AutoLoadDetails, CancellationContext
+from cloudshell.shell.core.session.cloudshell_session import CloudShellSessionContext
+import mock
+from data_model import *
+# run 'shellfoundry generate' to generate data model classes
+import subprocess
+import sys
+import os
+import importlib
+import paramiko
+
+# command = "./lanforge-scripts/py-scripts/update_dependencies.py"
+# print("running:[{}]".format(command))
+# process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
+# outs, errs = process.communicate()
+# print(outs)
+# print(errs)
+
+# if 'lanforge-scripts' not in sys.path:
+#     sys.path.append('./lanforge-scripts')
+
+# create_wanlink = importlib.import_module("lanforge-scripts.py-json.create_wanlink")
+# create_l3 = importlib.import_module("lanforge-scripts.py-scripts.create_l3")
+# CreateL3 = create_l3.CreateL3
+class LanforgeResourceDriver (ResourceDriverInterface):
+
+    def __init__(self):
+        """
+        ctor must be without arguments, it is created with reflection at run time
+        """
+        pass
+
+    def initialize(self, context):
+        """
+        Initialize the driver session, this function is called everytime a new instance of the driver is created
+        This is a good place to load and cache the driver configuration, initiate sessions etc.
+        :param InitCommandContext context: the context the command runs on
+        """
+        pass
+
+    def cleanup(self):
+        """
+        Destroy the driver session, this function is called everytime a driver instance is destroyed
+        This is a good place to close any open sessions, finish writing to log files
+        """
+        pass
+
+    def get_inventory(self, context):
+        """
+        Discovers the resource structure and attributes.
+        :param AutoLoadCommandContext context: the context the command runs on
+        :return Attribute and sub-resource information for the Shell resource you can return an AutoLoadDetails object
+        :rtype: AutoLoadDetails
+        """
+        # See below some example code demonstrating how to return the resource structure and attributes
+        # In real life, this code will be preceded by SNMP/other calls to the resource details and will not be static
+        # run 'shellfoundry generate' in order to create classes that represent your data model
+
+        '''
+        resource = LanforgeResource.create_from_context(context)
+        resource.vendor = 'specify the shell vendor'
+        resource.model = 'specify the shell model'
+
+        port1 = ResourcePort('Port 1')
+        port1.ipv4_address = '192.168.10.7'
+        resource.add_sub_resource('1', port1)
+
+        return resource.create_autoload_details()
+        '''
+        return AutoLoadDetails([], [])
+
+    def orchestration_save(self, context, cancellation_context, mode, custom_params):
+        """
+        Saves the Shell state and returns a description of the saved artifacts and information
+        This command is intended for API use only by sandbox orchestration scripts to implement
+        a save and restore workflow
+        :param ResourceCommandContext context: the context object containing resource and reservation info
+        :param CancellationContext cancellation_context: Object to signal a request for cancellation. Must be enabled in drivermetadata.xml as well
+        :param str mode: Snapshot save mode, can be one of two values 'shallow' (default) or 'deep'
+        :param str custom_params: Set of custom parameters for the save operation
+        :return: SavedResults serialized as JSON
+        :rtype: OrchestrationSaveResult
+        """
+
+        # See below an example implementation, here we use jsonpickle for serialization,
+        # to use this sample, you'll need to add jsonpickle to your requirements.txt file
+        # The JSON schema is defined at:
+        # https://github.com/QualiSystems/sandbox_orchestration_standard/blob/master/save%20%26%20restore/saved_artifact_info.schema.json
+        # You can find more information and examples examples in the spec document at
+        # https://github.com/QualiSystems/sandbox_orchestration_standard/blob/master/save%20%26%20restore/save%20%26%20restore%20standard.md
+        '''
+            # By convention, all dates should be UTC
+            created_date = datetime.datetime.utcnow()
+
+            # This can be any unique identifier which can later be used to retrieve the artifact
+            # such as filepath etc.
+
+            # By convention, all dates should be UTC
+            created_date = datetime.datetime.utcnow()
+
+            # This can be any unique identifier which can later be used to retrieve the artifact
+            # such as filepath etc.
+            identifier = created_date.strftime('%y_%m_%d %H_%M_%S_%f')
+
+            orchestration_saved_artifact = OrchestrationSavedArtifact('REPLACE_WITH_ARTIFACT_TYPE', identifier)
+
+            saved_artifacts_info = OrchestrationSavedArtifactInfo(
+                resource_name="some_resource",
+                created_date=created_date,
+                restore_rules=OrchestrationRestoreRules(requires_same_resource=True),
+                saved_artifact=orchestration_saved_artifact)
+
+            return OrchestrationSaveResult(saved_artifacts_info)
+      '''
+        pass
+
+    def orchestration_restore(self, context, cancellation_context, saved_artifact_info, custom_params):
+        """
+        Restores a saved artifact previously saved by this Shell driver using the orchestration_save function
+        :param ResourceCommandContext context: The context object for the command with resource and reservation info
+        :param CancellationContext cancellation_context: Object to signal a request for cancellation. Must be enabled in drivermetadata.xml as well
+        :param str saved_artifact_info: A JSON string representing the state to restore including saved artifacts and info
+        :param str custom_params: Set of custom parameters for the restore operation
+        :return: None
+        """
+        '''
+        # The saved_details JSON will be defined according to the JSON Schema and is the same object returned via the
+        # orchestration save function.
+        # Example input:
+        # {
+        #     "saved_artifact": {
+        #      "artifact_type": "REPLACE_WITH_ARTIFACT_TYPE",
+        #      "identifier": "16_08_09 11_21_35_657000"
+        #     },
+        #     "resource_name": "some_resource",
+        #     "restore_rules": {
+        #      "requires_same_resource": true
+        #     },
+        #     "created_date": "2016-08-09T11:21:35.657000"
+        #    }
+
+        # The example code below just parses and prints the saved artifact identifier
+        saved_details_object = json.loads(saved_details)
+        return saved_details_object[u'saved_artifact'][u'identifier']
+        '''
+        pass
+
+    def example_command(self, context):
+        """
+        this is my example command
+        :param ResourceCommandContext context
+        :return: str
+        """
+        resource = LanforgeResource.create_from_context(context)
+        msg = "My resource: " + resource.name
+        msg += ", at address: " + context.resource.address
+        return msg
+
+    def create_wanlink(self, context, name, latency, rate):
+        resource = LanforgeResource.create_from_context(context)
+        terminal_ip = context.resource.address
+        terminal_user = context.resource.attributes["{}User".format(shell_name)] = "lanforge"
+        terminal_pass = context.resource.attributes["{}Password".format(shell_name)] = "lanforge"
+
+        print("Initializing SSH connection to {ip}, with user {user} and password {password}".format(ip=terminal_ip, user=terminal_user, password=terminal_pass))
+        s = paramiko.SSHClient()
+        s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        s.connect(hostname=terminal_ip, username=terminal_user, password=terminal_pass)
+        
+        command = "/home/lanforge/lanforge-scripts/py-json/create_wanlink.py --host {host} --port_A {port_A} --port_B {port_B} --name \"{name}\" --latency \"{latency}\" --latency_A \"{latency_A}\" --latency_B \"{latency_B}\" --rate {rate} --rate_A {rate_A} --rate_B {rate_B} --jitter {jitter} --jitter_A {jitter_A} --jitter_B {jitter_B} --jitter_freq_A {jitter_freq_A} --jitter_freq_B {jitter_freq_B} --drop_A {drop_A} --drop_B {drop_B}".format(
+            host="localhost",
+            port_A="eth1",
+            port_B="eth2",
+            name=name,
+            latency=latency,
+            latency_A=latency,
+            latency_B=latency,
+            rate=rate,
+            rate_A=rate,
+            rate_B=rate,
+            jitter="0",
+            jitter_A="0",
+            jitter_B="0",
+            jitter_freq_A="0",
+            jitter_freq_B="0",
+            drop_A="0",
+            drop_B="0"
+        )
+        
+        (stdin, stdout, stderr) = s.exec_command(command)
+        output = ''
+        errors = ''
+        for line in stdout.readlines():
+            output += line
+        for line in stderr.readlines():
+            errors += line
+        print(errors)
+        # if errors != '':
+        print(errors)
+        # else:
+        print(output)
+        s.close()
+
+        # print(args)
+        # command = "./lanforge-scripts/py-json/create_wanlink.py --host \"{host}\" --name my_wanlink4 --latency \"{latency}\" --rate \"{rate}\"".format(
+        #     host = context.resource.address,
+        #     name=args['name'],
+        #     latency=args['latency'],
+        #     rate=args['rate']
+        # )
+        # print("running:[{}]".format(command))
+        # process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
+        # outs, errs = process.communicate()
+        # print(outs)
+        # print(errs)
+
+    def create_l3(self, context, name, min_rate_a, min_rate_b, endp_a, endp_b):
+        # api_session = CloudShellSessionContext(context)
+        resource = LanforgeResource.create_from_context(context)
+        args = {
+            "host": context.resource.address,
+            "name_prefix": name,
+            "min_rate_a": min_rate_a,
+            "min_rate_b": min_rate_b,
+            "endp_a": endp_a,
+            "endp_b": endp_b
+        }
+        # ip_var_test = CreateL3(
+        #     host=context.resource.address,
+        #     name_prefix=name,
+        #     endp_a=[endp_a],
+        #     endp_b=endp_b,
+        #     min_rate_a=min_rate_a,
+        #     min_rate_b=min_rate_b
+        # )
+
+        print(args)
+        terminal_ip = context.resource.address
+        terminal_user = context.resource.attributes["{}.User".format(shell_name)] = "lanforge"
+        terminal_pass = context.resource.attributes["{}.Password".format(shell_name)] = "lanforge"
+        
+        print("Initializing SSH connection to {ip}, with user {user} and password {password}".format(ip=terminal_ip, user=terminal_user, password=terminal_pass))
+        s = paramiko.SSHClient()
+        s.set_missing_host_key_policy(paramiko.AutoAddPolicy())
+        s.connect(hostname=terminal_ip, username=terminal_user, password=terminal_pass)
+        
+        command = "/home/lanforge/lanforge-scripts/py-scripts/create_l3.py --endp_a \"{endp_a}\" --endp_b \"{endp_b}\" --min_rate_a \"{min_rate_a}\" --min_rate_b \"{min_rate_b}\"".format(
+            endp_a=args['endp_a'],
+            endp_b=args['endp_b'],
+            min_rate_a=args['min_rate_a'],
+            min_rate_b=args['min_rate_b']
+        )
+        
+        (stdin, stdout, stderr) = s.exec_command(command)
+        output = ''
+        errors = ''
+        for line in stdout.readlines():
+            output += line
+        for line in stderr.readlines():
+            errors += line
+        print(errors)
+        if errors != '':
+            print(errors)
+        else:
+            print(output)
+        s.close()
+        
+        # print("running:[{}]".format(command))
+        # process = subprocess.Popen(command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, universal_newlines=True)
+        # outs, errs = process.communicate()
+        # print(outs)
+        # print(errs)
+
+        # num_sta = 2
+        # ip_var_test.pre_cleanup()
+        # ip_var_test.build()
+        # if not ip_var_test.passes():
+        #     print(ip_var_test.get_fail_message())
+        #     ip_var_test.exit_fail()
+        # print('Created %s stations and connections' % num_sta)
+
+if __name__ == "__main__":
+    # setup for mock-debug environment
+    shell_name = "LanforgeResource"
+    cancellation_context = mock.create_autospec(CancellationContext)
+    context = mock.create_autospec(ResourceCommandContext)
+    context.resource = mock.MagicMock()
+    context.reservation = mock.MagicMock()
+    context.connectivity = mock.MagicMock()
+    context.reservation.reservation_id = ""
+    context.resource.address = "192.168.100.176"
+    context.resource.name = "Lanforge_Resource"
+    context.resource.attributes = dict()
+    context.resource.attributes["{}.User".format(shell_name)] = "lanforge"
+    context.resource.attributes["{}.Password".format(shell_name)] = "lanforge"
+    context.resource.attributes["{}.SNMP Read Community".format(shell_name)] = ""
+
+    # add information for api connectivity
+    context.reservation.domain = "Global"
+    context.connectivity.server_address = "192.168.100.131"
+    driver = LanforgeResourceDriver()
+    # print driver.run_custom_command(context, custom_command="sh run", cancellation_context=cancellation_context)
+    # result = driver.example_command_with_api(context)
+
+    driver.create_l3(context, "my_fire", "69000", "41000", "eth1", "eth2")
+    driver.create_wanlink(context, name="my_wanlin", latency="49", rate="6000")
+    print("done")
diff --git a/Quali/lanforge_resource/src/drivermetadata.xml b/Quali/lanforge_resource/src/drivermetadata.xml
new file mode 100644
index 00000000..59cbeb1c
--- /dev/null
+++ b/Quali/lanforge_resource/src/drivermetadata.xml
@@ -0,0 +1,73 @@
+
+    
+	
+		
+		
+	
+	
+		
+	
+	
+		
+			
+				
+				
+				
+			
+			
+	
+	
+	  
+	    
+	      
+	      
+	      
+	      
+	      
+	    
+	  
+	
+    
+
diff --git a/Quali/lanforge_resource/src/importlib b/Quali/lanforge_resource/src/importlib
new file mode 100644
index 00000000..e69de29b
diff --git a/Quali/lanforge_resource/src/requirements.txt b/Quali/lanforge_resource/src/requirements.txt
new file mode 100644
index 00000000..8c1ba7fe
--- /dev/null
+++ b/Quali/lanforge_resource/src/requirements.txt
@@ -0,0 +1,25 @@
+mock
+cloudshell-shell-core>=5.0.3,<6.0.0
+cloudshell-automation-api
+pandas
+plotly
+numpy==1.16.6
+cryptography
+paramiko
+bokeh
+streamlit==0.62.0
+cython
+pyarrow==4.0.0
+websocket-client
+xlsxwriter
+pyshark
+influxdb
+influxdb-client
+matplotlib
+pdfkit
+pip-search
+pyserial
+pexpect-serial
+scp
+dash
+kaleido
diff --git a/Quali/lanforge_resource/src/sys b/Quali/lanforge_resource/src/sys
new file mode 100644
index 00000000..e69de29b
diff --git a/Quali/lanforge_resource/test_requirements.txt b/Quali/lanforge_resource/test_requirements.txt
new file mode 100644
index 00000000..7d963ba7
--- /dev/null
+++ b/Quali/lanforge_resource/test_requirements.txt
@@ -0,0 +1,7 @@
+nose
+coverage
+unittest2
+mock
+teamcity-messages
+jsonpickle
+nose-exclude
\ No newline at end of file
diff --git a/Quali/lanforge_resource/tests/__init__.py b/Quali/lanforge_resource/tests/__init__.py
new file mode 100644
index 00000000..40a96afc
--- /dev/null
+++ b/Quali/lanforge_resource/tests/__init__.py
@@ -0,0 +1 @@
+# -*- coding: utf-8 -*-
diff --git a/Quali/lanforge_resource/tests/test_lanforge-resource.py b/Quali/lanforge_resource/tests/test_lanforge-resource.py
new file mode 100644
index 00000000..de11d725
--- /dev/null
+++ b/Quali/lanforge_resource/tests/test_lanforge-resource.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+"""
+Tests for `LanforgeResourceDriver`
+"""
+
+import unittest
+
+from driver import LanforgeResourceDriver
+
+
+class TestLanforgeResourceDriver(unittest.TestCase):
+
+    def setUp(self):
+        pass
+
+    def tearDown(self):
+        pass
+
+    def test_000_something(self):
+        pass
+
+
+if __name__ == '__main__':
+    import sys
+    sys.exit(unittest.main())