diff --git a/geant/gap_ansible/playbooks/l2circuit.yaml b/geant/gap_ansible/playbooks/l2circuit.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..5f20a0fdd5c5c13048364aa95e7fd86387d21bb6
--- /dev/null
+++ b/geant/gap_ansible/playbooks/l2circuit.yaml
@@ -0,0 +1,18 @@
+- name: Manage L2circuits
+  hosts: all
+  gather_facts: false
+  tasks:
+    - name: Import group_vars/all
+      ansible.builtin.include_vars:
+        dir: /opt/ansible_inventory/group_vars/all
+
+    - name: Import standard variables for "{{ subscription.product.product_type }}/{{ subscription.layer_2_circuit_service_type | upper | replace(' ', '_') }}"
+      ansible.builtin.include_vars:
+        dir: /opt/ansible_inventory/geant_services/{{ subscription.product.product_type }}/{{ subscription.layer_2_circuit_service_type | upper | replace(' ', '_') }}
+
+    - name: Include l2circuit role
+      ansible.builtin.include_role:
+        name: l2circuits
+      loop: "{{ subscription.layer_2_circuit.layer_2_circuit_sides }}"
+      loop_control:
+        loop_var: l2c_side
diff --git a/geant/gap_ansible/roles/l2circuits/README.md b/geant/gap_ansible/roles/l2circuits/README.md
new file mode 100644
index 0000000000000000000000000000000000000000..cc977fe5a9393e55632712449e50f956c737335b
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/README.md
@@ -0,0 +1,29 @@
+# Role Name
+
+Role to manage l2circuits/epipes in multivendor network.
+
+## Requirements
+
+GEANT custom netconf module with Nokia "commit_comment" and "config_mode" features.
+
+## Role Variables
+
+- vars/main.yaml
+- external inventory (group_vars)
+- orchestrator (GSO)
+
+## Dependencies
+
+n/a
+
+## Example Playbook
+
+Role is supposed to be driven by GSO.
+
+## License
+
+MIT
+
+## Author Information
+
+A. Kurbatov, S. Spinelli. GEANT Orchestration and Automation Team (GOAT).
diff --git a/geant/gap_ansible/roles/l2circuits/defaults/main.yml b/geant/gap_ansible/roles/l2circuits/defaults/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..87a8dfd7c2ca643709810b33f3d19c1054e622f2
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/defaults/main.yml
@@ -0,0 +1,2 @@
+---
+is_verification_workflow: false
diff --git a/geant/gap_ansible/roles/l2circuits/handlers/main.yml b/geant/gap_ansible/roles/l2circuits/handlers/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..10433d11cf74ebb990a0ec3e5eed5d1bc491208d
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/handlers/main.yml
@@ -0,0 +1,2 @@
+---
+# handlers file for l2ciruits
diff --git a/geant/gap_ansible/roles/l2circuits/meta/main.yml b/geant/gap_ansible/roles/l2circuits/meta/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..8e604b5cb0805547e07ac3a9a3da3eaf8d89d0f8
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/meta/main.yml
@@ -0,0 +1,36 @@
+galaxy_info:
+  author: A. Kurbatov
+  description: GEANT Orchestration and Automation Team
+  company: GEANT
+
+  # If the issue tracker for your role is not on github, uncomment the
+  # next line and provide a value
+  # issue_tracker_url: http://example.com/issue/tracker
+
+  # Choose a valid license ID from https://spdx.org - some suggested licenses:
+  # - BSD-3-Clause (default)
+  # - MIT
+  # - GPL-2.0-or-later
+  # - GPL-3.0-only
+  # - Apache-2.0
+  # - CC-BY-4.0
+  license: MIT
+
+  min_ansible_version: "2.10"
+
+  # If this a Container Enabled role, provide the minimum Ansible Container version.
+  # min_ansible_container_version:
+
+  galaxy_tags:
+    - network
+    # List tags for your role here, one per line. A tag is a keyword that describes
+    # and categorizes the role. Users find roles by searching for tags. Be sure to
+    # remove the '[]' above, if you add tags to this list.
+    #
+    # NOTE: A tag is limited to a single word comprised of alphanumeric characters.
+    #       Maximum 20 tags per role.
+
+dependencies:
+  []
+  # List your role dependencies here, one per line. Be sure to remove the '[]' above,
+  # if you add dependencies to this list.
diff --git a/geant/gap_ansible/roles/l2circuits/tasks/compile.yaml b/geant/gap_ansible/roles/l2circuits/tasks/compile.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..86f50fbb0360f53919afeda3c994437eecfe0591
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/tasks/compile.yaml
@@ -0,0 +1,15 @@
+---
+- name: Set ansible host to localhost to compile template
+  ansible.builtin.set_fact:
+    ansible_host: "localhost"
+    ansible_connection: local
+
+- name: Print the template in "/var/tmp/ansible_run_{{ opid }}/{{ l2circuits_fqdn }}_l2c.conf"
+  # when: verb in ["deploy", "update", "terminate"]
+  ansible.builtin.template:
+    src: "{{ l2circuits_vendor }}/{{ verb }}/l2circuit.j2"
+    dest: "/var/tmp/ansible_run_{{ opid }}/{{ l2circuits_fqdn }}_l2c.conf"
+    lstrip_blocks: true
+    trim_blocks: true
+    mode: "0755"
+  delegate_to: localhost
diff --git a/geant/gap_ansible/roles/l2circuits/tasks/connection_tasks.yaml b/geant/gap_ansible/roles/l2circuits/tasks/connection_tasks.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..22de00d9e9a684eb6b1b26fd6bc4c2e1c1d11f58
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/tasks/connection_tasks.yaml
@@ -0,0 +1,16 @@
+---
+- name: Set ansible_host to terminal server when router is offline
+  ansible.builtin.set_fact:
+    ansible_host: "{{ l2circuits_router.router_site.site_ts_address }}"
+    ansible_port: "{{ l2circuits_router.router_ts_port }}"
+  when: l2circuits_router.router_access_via_ts | ansible.builtin.bool
+
+- name: Set ansible_host back to the {{ inventory_hostname }}
+  when: not l2circuits_router.router_access_via_ts | ansible.builtin.bool
+  ansible.builtin.set_fact:
+    ansible_host: "{{ inventory_hostname }}"
+
+- name: Load netconf connection config
+  ansible.builtin.set_fact:
+    ansible_connection: "{{ netconf_access[l2circuits_router.vendor].ansible_connection }}"
+    ansible_network_os: "{{ netconf_access[l2circuits_router.vendor].ansible_network_os }}"
diff --git a/geant/gap_ansible/roles/l2circuits/tasks/main.yml b/geant/gap_ansible/roles/l2circuits/tasks/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..85f499527bf20bf25117f3a1c3a8dbe8ca70b2c4
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/tasks/main.yml
@@ -0,0 +1,23 @@
+---
+# - name: Print l2c_side
+#   when: inventory_hostname == l2circuits_fqdn
+#   ansible.builtin.debug:
+#     var: l2c_side
+
+- name: Produce the config for the current l2circuit endpoint
+  when: inventory_hostname == l2circuits_fqdn
+  block:
+    - name: Include standard tasks
+      ansible.builtin.include_tasks: standard_tasks.yaml
+
+    - name: Merge vars
+      ansible.builtin.include_tasks: merge_vars.yaml
+
+    - name: Compile template
+      ansible.builtin.include_tasks: compile.yaml
+
+    - name: Include connecion tasks
+      ansible.builtin.include_tasks: connection_tasks.yaml
+
+    - name: Push config to the router
+      ansible.builtin.include_tasks: push_config.yaml
diff --git a/geant/gap_ansible/roles/l2circuits/tasks/merge_vars.yaml b/geant/gap_ansible/roles/l2circuits/tasks/merge_vars.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..589581022dc724248118bfdded36b6bff918968c
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/tasks/merge_vars.yaml
@@ -0,0 +1,22 @@
+---
+- name: Load info for the remote l2circuit endpoint
+  ansible.builtin.set_fact:
+    remote_side: "{{ subscription | community.general.json_query(qry) }}"
+  vars:
+    qry: "layer_2_circuit.layer_2_circuit_sides[?sbp.edge_port.node.router_fqdn != '{{ inventory_hostname }}'] | [0]"
+
+- name: Load info for the local l2circuit endpoint
+  ansible.builtin.set_fact:
+    local_side: "{{ subscription | community.general.json_query(qry) }}"
+  vars:
+    qry: "layer_2_circuit.layer_2_circuit_sides[?sbp.edge_port.node.router_fqdn == '{{ inventory_hostname }}'] | [0]"
+
+- name: Set partner names for the l2circuit endpoints
+  ansible.builtin.set_fact:
+    local_partner_name: "{{ local_side.sbp.edge_port.partner_name }}"
+    remote_partner_name: "{{ remote_side.sbp.edge_port.partner_name }}"
+
+- name: Calculate SDP to use locally
+  ansible.builtin.set_fact:
+    sdp_id: "{{ remote_side.sbp.edge_port.node.router_lo_ipv4_address |
+      replace(sdp_prefix_regex, '') | replace('.', '') + l2circuits_sdp_type }}"
diff --git a/geant/gap_ansible/roles/l2circuits/tasks/push_config.yaml b/geant/gap_ansible/roles/l2circuits/tasks/push_config.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..3153a8cb9fe29fecdd1ebf02d898f5385a3d146f
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/tasks/push_config.yaml
@@ -0,0 +1,64 @@
+---
+- name: Config deploy [CHECK ONLY][NOKIA]
+  when: >
+    dry_run | ansible.builtin.bool
+    and
+    l2circuits_vendor == "nokia"
+  geant.gap_ansible.nokia_netconf_config:
+    format: xml
+    default_operation: merge
+    content: "{{ lookup('ansible.builtin.file', '/var/tmp/ansible_run_{{ opid }}/{{ l2circuits_fqdn }}_l2c.conf') }}"
+    commit: true
+    validate: true
+    config_mode: private
+  diff: true
+  register: output
+  check_mode: true
+
+# - name: Fail if config diff is detected
+#   when: >
+#     output.changed | ansible.builtin.bool
+#     and
+#     is_verification_workflow | ansible.builtin.bool
+#   ansible.builtin.fail:
+#     msg: >
+#       The config for {{ subscription.description }} has drifted!
+
+- name: Config deploy [REAL][NOKIA]
+  when: >
+    not dry_run | ansible.builtin.bool
+    and
+    l2circuits_vendor == "nokia"
+  geant.gap_ansible.nokia_netconf_config:
+    format: xml
+    default_operation: merge
+    content: "{{ lookup('ansible.builtin.file', '/var/tmp/ansible_run_{{ opid }}/{{ l2circuits_fqdn }}_l2c.conf') }}"
+    commit: true
+    commit_comment: "{{ commit_comment }}"
+    config_mode: private
+  diff: true
+  register: output
+
+- name: Config deploy [CHECK ONLY][JUNIPER]
+  when: >
+    dry_run | ansible.builtin.bool
+    and
+    l2circuits_vendor == "juniper"
+  junipernetworks.junos.junos_config:
+    update: "replace"
+    src: "/var/tmp/ansible_run_{{ opid }}/{{ l2circuits_fqdn }}_l2c.conf"
+    src_format: set
+    check_commit: true
+  diff: true
+
+- name: Config deploy [REAL][JUNIPER]
+  when: >
+    not dry_run | ansible.builtin.bool
+    and
+    l2circuits_vendor == "juniper"
+  junipernetworks.junos.junos_config:
+    update: "replace"
+    src: "/var/tmp/ansible_run_{{ opid }}/{{ l2circuits_fqdn }}_l2c.conf"
+    src_format: set
+    comment: "{{ commit_comment }}"
+  diff: true
diff --git a/geant/gap_ansible/roles/l2circuits/tasks/standard_tasks.yaml b/geant/gap_ansible/roles/l2circuits/tasks/standard_tasks.yaml
new file mode 100644
index 0000000000000000000000000000000000000000..4772e5bba9ad6a0f39f7a8cc6bd4b019e4cf568d
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/tasks/standard_tasks.yaml
@@ -0,0 +1,15 @@
+---
+- name: Generate an ID for this run
+  ansible.builtin.set_fact:
+    opid: "{{ lookup('community.general.random_string', length=18, special=false) }}"
+
+- name: Print the ID
+  ansible.builtin.debug:
+    msg: "{{ opid }}"
+
+- name: Create a folder for all compiled output
+  ansible.builtin.file:
+    path: "/var/tmp/ansible_run_{{ opid }}"
+    state: directory
+    mode: "0755"
+  delegate_to: localhost
diff --git a/geant/gap_ansible/roles/l2circuits/templates/juniper/deploy/l2circuit.j2 b/geant/gap_ansible/roles/l2circuits/templates/juniper/deploy/l2circuit.j2
new file mode 100644
index 0000000000000000000000000000000000000000..e402148551c5a837b125d7189e91609268ea46fb
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/templates/juniper/deploy/l2circuit.j2
@@ -0,0 +1,11 @@
+set interfaces {{ l2circuits_lag_name }} unit {{ l2circuits_vlan }} description "SRV_L2CIRCUIT CUSTOMER {{ local_partner_name | upper }} {{ remote_partner_name | upper }} #{{ l2circuits_custom_service_name | replace(' ', '_') }} ${{ local_side.sbp.gs_id }}"
+set interfaces {{ l2circuits_lag_name }} unit {{ l2circuits_vlan }} encapsulation vlan-ccc
+set interfaces {{ l2circuits_lag_name }} unit {{ l2circuits_vlan }} vlan-id {{ l2circuits_vlan }}
+set interfaces {{ l2circuits_lag_name }} unit {{ l2circuits_vlan }} family ccc
+set interfaces {{ l2circuits_lag_name }} unit {{ l2circuits_vlan }} input-vlan-map pop
+set interfaces {{ l2circuits_lag_name }} unit {{ l2circuits_vlan }} output-vlan-map push
+
+set protocols l2circuit neighbor {{ remote_side.sbp.edge_port.node.router_lo_ipv4_address }} interface {{ l2circuits_lag_name }}.{{ l2circuits_vlan }} virtual-circuit-id {{ l2circuits_l2c_vcid }}
+set protocols l2circuit neighbor {{ remote_side.sbp.edge_port.node.router_lo_ipv4_address }} interface {{ l2circuits_lag_name }}.{{ l2circuits_vlan }} control-word
+set protocols l2circuit neighbor {{ remote_side.sbp.edge_port.node.router_lo_ipv4_address }} interface {{ l2circuits_lag_name }}.{{ l2circuits_vlan }} mtu {{ service_mtu.juniper }}
+
diff --git a/geant/gap_ansible/roles/l2circuits/templates/juniper/terminate/l2circuit.j2 b/geant/gap_ansible/roles/l2circuits/templates/juniper/terminate/l2circuit.j2
new file mode 100644
index 0000000000000000000000000000000000000000..067d5114c8b73e46dc1024830a521029491f3168
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/templates/juniper/terminate/l2circuit.j2
@@ -0,0 +1,2 @@
+delete protocols l2circuit neighbor {{ remote_side.sbp.edge_port.node.router_lo_ipv4_address }} interface {{ l2circuits_lag_name }}.{{ l2circuits_vlan }}
+delete interfaces {{ l2circuits_lag_name }} unit {{ l2circuits_vlan }}
diff --git a/geant/gap_ansible/roles/l2circuits/templates/nokia/deploy/l2circuit.j2 b/geant/gap_ansible/roles/l2circuits/templates/nokia/deploy/l2circuit.j2
new file mode 100644
index 0000000000000000000000000000000000000000..6d8567baa8cf3029c5f4a7bb51f3f5ccd8d77ab5
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/templates/nokia/deploy/l2circuit.j2
@@ -0,0 +1,49 @@
+{#{% if l2circuits_is_standalone_run %}#}
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:alu="urn:ietf:params:xml:ns:netconf:base:1.0">
+    <configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf">
+
+{% if l2circuits_l2c_type == 'VLAN' %}
+        <connection-profile xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes" alu:operation="replace">
+            <vlan>
+                <connection-profile-id>{{ l2circuits_l2c_vcid }}</connection-profile-id>
+                <qtag-range>
+                  <start>{{ subscription.layer_2_circuit.vlan_range_lower_bound }}</start>
+                  <end>{{ subscription.layer_2_circuit.vlan_range_upper_bound }}</end>
+                </qtag-range>
+            </vlan>
+        </connection-profile>
+{% endif %}
+
+        <service xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes">
+            <epipe alu:operation="replace">
+            <service-name >EPIPE:{{ l2circuits_custom_service_name | replace(' ', '_') }}:{{ l2circuits_l2c_vcid }}</service-name>
+                <admin-state>enable</admin-state>
+                <description>SRV_L2CIRCUIT CUSTOMER {{ local_partner_name | upper }} {{ remote_partner_name | upper }} #{{ l2circuits_custom_service_name | replace(' ', '_') }} ${{ local_side.sbp.gs_id }}</description>
+                <service-id>{{ l2circuits_l2c_vcid }}</service-id>
+                <customer>1</customer>
+                <vpn-id>{{ l2circuits_l2c_vcid }}</vpn-id>
+                <service-mtu>{{ service_mtu.nokia }}</service-mtu>
+                <ignore-l2vpn-mtu-mismatch>false</ignore-l2vpn-mtu-mismatch>
+                <spoke-sdp>
+                  <sdp-bind-id>{{ sdp_id }}:{{ l2circuits_l2c_vcid }}</sdp-bind-id>
+                    <admin-state>enable</admin-state>
+                    <control-word>true</control-word>
+                    <vc-type>{{ 'ether' if l2circuits_l2c_type == 'Ethernet' else 'vlan' }}</vc-type>
+                    <pw-status>
+                        <signaling>true</signaling>
+                    </pw-status>
+                </spoke-sdp>
+                <sap>
+                    <description>{{ local_partner_name | upper }}:{{ l2circuits_vlan }}:{{ local_side.sbp.gs_id }}</description>
+                    {% if l2circuits_l2c_type == 'Ethernet' %}
+                    <sap-id>{{ l2circuits_lag_name }}:{{ l2circuits_vlan }}</sap-id>
+                    {% else %}
+                    <sap-id>{{ l2circuits_lag_name }}:cp-{{ l2circuits_l2c_vcid }}</sap-id>
+                    {% endif %}
+                    <admin-state>enable</admin-state>
+                </sap>
+            </epipe>
+        </service>
+
+   </configure>
+</config>
diff --git a/geant/gap_ansible/roles/l2circuits/templates/nokia/terminate/l2circuit.j2 b/geant/gap_ansible/roles/l2circuits/templates/nokia/terminate/l2circuit.j2
new file mode 100644
index 0000000000000000000000000000000000000000..eb28e6a96209b036cf1aa4c195f9e87236041be1
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/templates/nokia/terminate/l2circuit.j2
@@ -0,0 +1,19 @@
+<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:alu="urn:ietf:params:xml:ns:netconf:base:1.0">
+    <configure xmlns="urn:nokia.com:sros:ns:yang:sr:conf">
+
+{% if l2circuits_l2c_type == 'VLAN' %}
+        <connection-profile xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes" alu:operation="delete">
+            <vlan>
+                <connection-profile-id>{{ l2circuits_l2c_vcid }}</connection-profile-id>
+            </vlan>
+        </connection-profile>
+{% endif %}
+
+<service xmlns="urn:nokia.com:sros:ns:yang:sr:conf" xmlns:nc="urn:ietf:params:xml:ns:netconf:base:1.0" xmlns:nokia-attr="urn:nokia.com:sros:ns:yang:sr:attributes">
+    <epipe alu:operation="delete">
+    <service-name>EPIPE:{{ l2circuits_custom_service_name | replace(' ', '_') }}:{{ l2circuits_l2c_vcid }}</service-name>
+    </epipe>
+</service>
+
+   </configure>
+</config>
diff --git a/geant/gap_ansible/roles/l2circuits/vars/main.yml b/geant/gap_ansible/roles/l2circuits/vars/main.yml
new file mode 100644
index 0000000000000000000000000000000000000000..9380163eadc7eab68f42813dec8c5b2bb6654ed7
--- /dev/null
+++ b/geant/gap_ansible/roles/l2circuits/vars/main.yml
@@ -0,0 +1,12 @@
+---
+l2circuits_is_standalone_run: false
+
+l2circuits_sdp_type: "1"
+l2circuits_router: "{{ l2c_side.sbp.edge_port.node }}"
+l2circuits_vendor: "{{ l2c_side.sbp.edge_port.node.vendor }}"
+l2circuits_fqdn: "{{ l2c_side.sbp.edge_port.node.router_fqdn }}"
+l2circuits_lag_name: "{{ l2c_side.sbp.edge_port.edge_port_name }}"
+l2circuits_vlan: "{{ l2c_side.sbp.vlan_id }}"
+l2circuits_l2c_type: "{{ subscription.layer_2_circuit.layer_2_circuit_type }}"
+l2circuits_l2c_vcid: "{{ subscription.layer_2_circuit.virtual_circuit_id }}"
+l2circuits_custom_service_name: "{{ subscription.layer_2_circuit.custom_service_name }}"