Skip to content
This repository has been archived by the owner on Jan 24, 2023. It is now read-only.

Commit

Permalink
Merge pull request #129 from smart-edge-open/smart-edge-open-release-…
Browse files Browse the repository at this point in the history
…2109

Smart Edge Open 21.09 release
  • Loading branch information
cjnolan authored Sep 30, 2021
2 parents 1878cfc + c3557af commit ad7b9ba
Show file tree
Hide file tree
Showing 38 changed files with 318 additions and 518 deletions.
4 changes: 2 additions & 2 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ verify_ssl = true
name = "pypi"

[packages]
ansible = "==2.9.18"
ansible = "==2.9.20"
ansible-lint = "==4.2.0"
jinja2 = "==2.11.3"
pylint = "==2.7.2"
netaddr = "==0.7.18"
sh = "==1.14.1"
# Force 3.2 due to security vulnabilities in 3.4.6
cryptography = "==3.2"
cryptography = "==3.3.2"

[requires]
python_version = "3.6"
391 changes: 196 additions & 195 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@ SPDX-License-Identifier: Apache-2.0
Copyright (c) 2019 Intel Corporation
```

For documentation please refer to https://github.com/open-ness/specs/blob/master/doc/getting-started/converged-edge-experience-kits.md
For documentation please refer to https://github.com/smart-edge-open/specs/blob/master/doc/getting-started/converged-edge-experience-kits.md
16 changes: 7 additions & 9 deletions cloud/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@ SPDX-License-Identifier: Apache-2.0
Copyright (c) 2020 Intel Corporation
```

![OpenNESS](https://www.openness.org/images/openness-logo.png)
# Smart Edge Open Devkit for Azure

# OpenNESS Devkit for Azure

This is an Azure Resource Manager template to deploy OpenNESS within Azure.
This is an Azure Resource Manager template to deploy Smart Edge Open within Azure.

## Requirements

Expand Down Expand Up @@ -37,7 +35,7 @@ The following fields **must** be populated within the Azure portal:
> NOTE: The Deploy to Azure button may only work when clicked within Github web interface
[![Deploy To Azure](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fopen-ness%2Fconverged-edge-experience-kits%2Fmaster%2Fcloud%2Fazuredeploy.json)
[![Deploy To Azure](https://raw.githubusercontent.com/Azure/azure-quickstart-templates/master/1-CONTRIBUTION-GUIDE/images/deploytoazure.svg?sanitize=true)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fsmart-edge-open%2Fconverged-edge-experience-kits%2Fmaster%2Fcloud%2Fazuredeploy.json)

## Post Deployment

Expand All @@ -47,9 +45,9 @@ After deployment completes, the Azure Deployment resource will have Outputs with

The "result" field will include access instructions for the deployed cluster, as well as the Ansible _Play Recap_.

> NOTE: If the recap includes a failure count other than `failed=0` then the OpenNESS installation failed.
> NOTE: If the recap includes a failure count other than `failed=0` then the Smart Edge Open installation failed.
The OpenNESS installation log and the Ansible inventory file will be available on the Controller Node in `~/openness-install.log` and `~/inventory.yml` within the user specified non-root user account (e.g. `ceekuser`).
The Smart Edge Open installation log and the Ansible inventory file will be available on the Controller Node in `~/smartEdgeOpen-install.log` and `~/inventory.yml` within the user specified non-root user account (e.g. `ceekuser`).

The public IP addresses for the nodes can be queried with this script, your local `bash` shell with the presence of [Azure CLI](https://docs.microsoft.com/en-us/cli/azure/install-azure-cli) or an [Azure Cloud Shell](https://docs.microsoft.com/en-us/azure/cloud-shell/overview). You will need to manually confirm you have an active Azure token, the easist method is by manually running `az login` prior to execution:

Expand All @@ -66,6 +64,6 @@ az vmss list-instance-public-ips -n "$vmssName" -g "$resourceGroupName" --query

You can now proceed to onboarding applications to your Devkit environment.

If you are looking to integrate your own application with OpenNESS please start at our [Network Edge Applications Onboarding](https://www.openness.org/docs/doc/applications-onboard/network-edge-applications-onboarding) guide.
If you are looking to integrate your own application with Smart Edge Open please start at our [Network Edge Applications Onboarding](https://www.openness.org/docs/doc/applications-onboard/network-edge-applications-onboarding) guide.

You can find OpenNESS existing integrated apps within our [edgeapps repo](https://github.com/open-ness/edgeapps) and our [Commercial Edge Applications portal](https://networkbuilders.intel.com/commercial-applications), or you can [participate and have your apps featured](https://networkbuilders.intel.com/commercial-applications/participate).
You can find Smart Edge Open existing integrated apps within our [edgeapps repo](https://github.com/smart-edge-open/edgeapps) and our [Commercial Edge Applications portal](https://networkbuilders.intel.com/commercial-applications), or you can [participate and have your apps featured](https://networkbuilders.intel.com/commercial-applications/participate).
24 changes: 12 additions & 12 deletions cloud/azuredeploy.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"opennessRepository": {
"smartEdgeOpenRepository": {
"type": "string",
"defaultValue": "https://github.com/open-ness/converged-edge-experience-kits",
"defaultValue": "https://github.com/smart-edge-open/converged-edge-experience-kits",
"allowedValues": [
"https://github.com/open-ness/converged-edge-experience-kits"
"https://github.com/smart-edge-open/converged-edge-experience-kits"
],
"metadata": {
"description": "(Required) OpenNESS experience kit repository url"
"description": "(Required) Smart Edge Open experience kit repository url"
}
},
"flavor": {
Expand All @@ -20,7 +20,7 @@
"central_orchestrator"
],
"metadata": {
"description": "(Required) OpenNESS experience kit flavor (must include kernel_skip=true)"
"description": "(Required) Smart Edge Open experience kit flavor (must include kernel_skip=true)"
}
},
"sshIdentity": {
Expand Down Expand Up @@ -93,7 +93,7 @@
},
"variables": {
"vmDomainName": "[concat('ceek-', uniqueString(resourceGroup().id, deployment().name))]",
"identityName": "[concat('deployOpenNESS-', uniqueString(resourceGroup().id, deployment().name))]",
"identityName": "[concat('deploySmartEdgeOpen-', uniqueString(resourceGroup().id, deployment().name))]",
"roleDefinitionId": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Authorization/roleDefinitions/', 'b24988ac-6180-42a0-ab88-20f7382dd24c')]",
"roleDefinitionName": "[guid(variables('identityName'), variables('roleDefinitionId'))]"
},
Expand Down Expand Up @@ -134,7 +134,7 @@
{
"type": "Microsoft.Resources/deploymentScripts",
"apiVersion": "2019-10-01-preview",
"name": "setupOpenNESS",
"name": "setupSmartEdgeOpen",
"location": "[resourceGroup().location]",
"dependsOn": [
"[variables('roleDefinitionName')]"
Expand All @@ -150,7 +150,7 @@
"environmentVariables": [
{
"name": "GIT_REPO",
"value": "[parameters('opennessRepository')]"
"value": "[parameters('smartEdgeOpenRepository')]"
},
{
"name": "GIT_TOKEN",
Expand Down Expand Up @@ -210,10 +210,10 @@
}
],
"azCliVersion": "2.9.1",
"scriptContent": "set -xe; pip install --upgrade pip > /dev/null; pip install ansible==2.9.18 > /dev/null; pip install sh==1.12.14 > /dev/null; pip install netaddr==0.8.0 > /dev/null; pip install PyYAML==5.4 > /dev/null; az login --identity -u \"${AZ_SCRIPTS_USER_ASSIGNED_IDENTITY}\"; az vmss create --resource-group=\"${AZ_RESOURCE_GROUP}\" --name=\"${AZ_VMSS}\" --image=\"${AZ_VM_IMAGE}\" --vm-sku=\"${AZ_VM_FLAVOR}\" --os-disk-size-gb=\"${AZ_VM_DISK_SIZE}\" --instance-count=\"${AZ_VM_COUNT}\" --public-ip-per-vm --generate-ssh-keys --vm-domain-name=\"${AZ_VMDOMAIN}\" --admin-username=\"${AZ_VM_USERNAME}\" --load-balancer=\"\" --disable-overprovision;cd /root; cp ${AZ_SCRIPTS_PATH_INPUT_DIRECTORY}/install.sh .; cp ${AZ_SCRIPTS_PATH_INPUT_DIRECTORY}/ceek_setup.py .; ./install.sh 2>&1| tee install.log $AZ_SCRIPTS_PATH_OUTPUT_DIRECTORY/install.log; cp ~/ceek/inventory.yml $AZ_SCRIPTS_PATH_OUTPUT_DIRECTORY; SUMMARY=`tail -6 /root/install.log | sed 's/[*]//g'` ; IP=`awk '/controller/{getline; match($0,/[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+/); ip = substr($0,RSTART,RLENGTH); printf \"%s\" ip}' $AZ_SCRIPTS_PATH_OUTPUT_DIRECTORY/inventory.yml` ; scp /root/install.log $AZ_VM_USERNAME@$IP:~/openness-install.log ; scp ~/ceek/inventory.yml $AZ_VM_USERNAME@$IP:inventory.yml; echo -e \"Access Controller Node: ssh $AZ_VM_USERNAME@$IP\\nInteract with Kubernetes using 'kubectl' as root user\\n$SUMMARY \" | jq -Rs '{Result: split(\"\n\")}' > $AZ_SCRIPTS_OUTPUT_PATH ",
"scriptContent": "set -xe; pip install --upgrade pip > /dev/null; pip install ansible==2.9.18 > /dev/null; pip install sh==1.12.14 > /dev/null; pip install netaddr==0.8.0 > /dev/null; pip install PyYAML==5.4 > /dev/null; az login --identity -u \"${AZ_SCRIPTS_USER_ASSIGNED_IDENTITY}\"; az vmss create --resource-group=\"${AZ_RESOURCE_GROUP}\" --name=\"${AZ_VMSS}\" --image=\"${AZ_VM_IMAGE}\" --vm-sku=\"${AZ_VM_FLAVOR}\" --os-disk-size-gb=\"${AZ_VM_DISK_SIZE}\" --instance-count=\"${AZ_VM_COUNT}\" --public-ip-per-vm --generate-ssh-keys --vm-domain-name=\"${AZ_VMDOMAIN}\" --admin-username=\"${AZ_VM_USERNAME}\" --load-balancer=\"\" --disable-overprovision;cd /root; cp ${AZ_SCRIPTS_PATH_INPUT_DIRECTORY}/install.sh .; cp ${AZ_SCRIPTS_PATH_INPUT_DIRECTORY}/ceek_setup.py .; ./install.sh 2>&1| tee install.log $AZ_SCRIPTS_PATH_OUTPUT_DIRECTORY/install.log; cp ~/ceek/inventory.yml $AZ_SCRIPTS_PATH_OUTPUT_DIRECTORY; SUMMARY=`tail -6 /root/install.log | sed 's/[*]//g'` ; IP=`awk '/controller/{getline; match($0,/[0-9]+\\.[0-9]+\\.[0-9]+\\.[0-9]+/); ip = substr($0,RSTART,RLENGTH); printf \"%s\" ip}' $AZ_SCRIPTS_PATH_OUTPUT_DIRECTORY/inventory.yml` ; scp /root/install.log $AZ_VM_USERNAME@$IP:~/smartEdgeOpen-install.log ; scp ~/ceek/inventory.yml $AZ_VM_USERNAME@$IP:inventory.yml; echo -e \"Access Controller Node: ssh $AZ_VM_USERNAME@$IP\\nInteract with Kubernetes using 'kubectl' as root user\\n$SUMMARY \" | jq -Rs '{Result: split(\"\n\")}' > $AZ_SCRIPTS_OUTPUT_PATH ",
"supportingScriptUris": [
"https://raw.githubusercontent.com/open-ness/converged-edge-experience-kits/master/cloud/install.sh",
"https://raw.githubusercontent.com/open-ness/converged-edge-experience-kits/master/cloud/ceek_setup.py"
"https://raw.githubusercontent.com/smart-edge-open/converged-edge-experience-kits/master/cloud/install.sh",
"https://raw.githubusercontent.com/smart-edge-open/converged-edge-experience-kits/master/cloud/ceek_setup.py"
],
"cleanupPreference": "OnSuccess",
"retentionInterval": "PT26H",
Expand All @@ -224,7 +224,7 @@
"outputs": {
"result": {
"type": "object",
"value": "[reference('setupOpenNESS').outputs]"
"value": "[reference('setupSmartEdgeOpen').outputs]"
},
"VMUserName": {
"type": "string",
Expand Down
12 changes: 6 additions & 6 deletions cloud/ceek_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ def make_parser():

parser.add_argument(
"-r", "--repo", action="store", metavar="GIT_REPO", dest="git_repo",
default="https://github.com/open-ness/converged-edge-experience-kits",
help="OpenNESS converged edge experience kit repository")
default="https://github.com/smart-edge-open/converged-edge-experience-kits",
help="Smart Edge Open converged edge experience kit repository")
parser.add_argument(
"-t", "--token", action="store", metavar="GIT_TOKEN", dest="git_token",
default="",
Expand All @@ -42,13 +42,13 @@ def make_parser():
help="Username used by ansible automation scripts")
parser.add_argument(
"-o", "--ceek-vars", action="store", metavar="CEEK_VARS", dest="ceek_vars",
help="Extra OpenNESS experience kit variables to override the defaults")
help="Extra Smart Edge Open experience kit variables to override the defaults")
parser.add_argument(
"-f", "--flavor", action="store", metavar="CEEK_FLAVOR", dest="ceek_flavor",
help="OpenNESS experience kit flavor")
help="Smart Edge Open experience kit flavor")
parser.add_argument(
"-l", "--limits", action="store", metavar="ANSIBLE_LIMITS", dest="ansible_limits",
help="OpenNESS experience kit ansible limits")
help="Smart Edge Open experience kit ansible limits")

parser.add_argument(
"-v", "--verbosity", action="store", metavar="LEVEL", dest="verbosity", default="INFO",
Expand All @@ -64,7 +64,7 @@ def make_parser():

def setup_logger(options):
"""Configure Python logging module"""
log_fmt = "OpenNESS Setup: [%(levelname)s] %(module)s(%(lineno)d): %(message)s"
log_fmt = "Smart Edge Open Setup: [%(levelname)s] %(module)s(%(lineno)d): %(message)s"
ts_fmt = "%Y-%m-%dT%H:%M:%S"

handler = logging.StreamHandler()
Expand Down
6 changes: 4 additions & 2 deletions deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,14 +198,16 @@ def run_deployment(inventory, cleanup=False):
inventory.flavor,
inventory.cluster_name,
playbook_basename)
log_file = open(deployment_log_file_path, "a+")
# pylint disable because log_file is a long living object.
log_file = open(deployment_log_file_path, "a+") # pylint: disable=bad-option-value,consider-using-with

logging.info('%s %s: command: "%s"',
inventory.cluster_name, playbook_basename, ansible_playbook_command)
logging.info('%s %s: log file: "%s"',
inventory.cluster_name, playbook_basename, os.path.realpath(log_file.name))

deployment_process = subprocess.Popen(ansible_playbook_command.split(),
# pylint disable because deployment_process is a long living object.
deployment_process = subprocess.Popen(ansible_playbook_command.split(), # pylint: disable=bad-option-value,consider-using-with
stdout=log_file, stderr=subprocess.STDOUT)

return DeploymentWrapper(process=deployment_process,
Expand Down
4 changes: 2 additions & 2 deletions inventory/default/group_vars/all/10-default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@

### GitHub token
# It must be provided when using private repositories.
# Not required when using github.com/open-ness repositories.
# Not required when using github.com/smart-edge-open repositories.
# How to create a GitHub token: https://help.github.com/en/github/authenticating-to-github/creating-a-personal-access-token-for-the-command-line
git_repo_token: ""

Expand Down Expand Up @@ -41,7 +41,7 @@ os_remove_yum_plugins: true

### OpenNESS Git Repository
# Following variable specify branch/SHA/tag to be checked out for the source repository
git_repo_branch: openness-21.03.05
git_repo_branch: smart-edge-open-21.09

# If True, the repository will be deleted and cloned again
# If False, repository will be left as it is and any changes won't be overwritten.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ os_yum_base_packages:
## URLs to docker images saved with `docker save <image>:<ver> | gzip > <image>.tar.gz` that are going to be preloaded after docker setup
docker_images: []

git_repo_url: https://{{ git_repo_token }}@github.com/open-ness/edgeservices.git
git_repo_url: https://{{ git_repo_token }}@github.com/smart-edge-open/edgeservices.git
_git_repo_dest: "{{ openness_dir }}/edgeservices"

## Network Edge Helm Charts Storage Default Directory
Expand Down
2 changes: 1 addition & 1 deletion inventory/default/group_vars/edgenode_group/10-default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -88,5 +88,5 @@ _os_yum_exclude_rpm_packages: "exclude=kernel-3* kernel-rt* kernel-rt-kvm* kerne
# URLs to docker images saved with `docker save <image>:<ver> | gzip > <image>.tar.gz` that are going to be preloaded after docker setup
docker_images: []

git_repo_url: https://{{ git_repo_token }}@github.com/open-ness/edgeservices.git
git_repo_url: https://{{ git_repo_token }}@github.com/smart-edge-open/edgeservices.git
_git_repo_dest: "{{ openness_dir }}/edgeservices"
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ os_remove_yum_plugins: true
## URLs to docker images saved with `docker save <image>:<ver> | gzip > <image>.tar.gz` that are going to be preloaded after docker setup
docker_images: []

git_repo_url: https://{{ git_repo_token }}@github.com/open-ness/edgeservices.git
git_repo_branch: openness-21.03.05
git_repo_url: https://{{ git_repo_token }}@github.com/smart-edge-open/edgeservices.git
git_repo_branch: smart-edge-open-21.09
_git_repo_dest: "{{ openness_dir }}/edgeservices"
40 changes: 0 additions & 40 deletions inventory/default/host_vars/node01/10-default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,43 +2,3 @@
# Copyright (c) 2019-2020 Intel Corporation

---

sriov:
network_interfaces: {}
interface_subnets: []
interface_ips: []
network_name: []
vm_vf_ports: 0

# ptp_port keeps the host's interface(s) connected to:
# - a PTP MASTER node, in this case it should keep only one interface name, e.g.:
# ptp_port: enp134s0f1
# - PTP SLAVE(S), then should store a list of interfaces connected to slaves, e.g.:
# ptp_port:
# - enp134s0f1
# - Grand Master only for single node setup, then it should keep one interface name, e.g.:
# ptp_port: enp134s0f1
ptp_port: ""

# ptp_port_gm is the host's interface connected to a Grand Master, e.g.:
# ptp_port_gm: enp134s0f0
ptp_port_gm:

# ptp_network_transport keeps network transport for ptp.
# Valid options:
# -2 Select the IEEE 802.3 network transport.
# -4 Select the UDP IPv4 network transport.
ptp_network_transport: "-2"

# Grand Master IP, e.g. (set this value for the single node setup):
# gm_ip: "169.254.99.9"
gm_ip: ""

# Set the following values for the single node setup.
# If DHCP support on GMC is not enabled:
# - ptp_port_ip contains a static IP for the server port connected to GMC, e.g.:
# ptp_port_ip: "169.254.99.175"
# - ptp_port_cidr - CIDR for IP from, e.g.:
# ptp_port_cidr: "24"
ptp_port_ip: ""
ptp_port_cidr: ""
8 changes: 0 additions & 8 deletions network_edge_cleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -228,14 +228,6 @@
tasks:
# biosfw/node - no clean up, because build image is deleted when running docker's prune.yml

- name: cleanup acc100 dpdk init app
block:
- name: load acc100 dpdk init app variables
include_vars: ./roles/infrastructure/init_app_acc100/defaults/main.yml
- name: load acc100 dpdk init app
include_tasks: ./roles/infrastructure/init_app_acc100/tasks/cleanup.yml
when: acc100_userspace_vf.enabled | default(False)

- name: cleanup QAT device plugin
block:
- name: load QAT DP variables
Expand Down
24 changes: 14 additions & 10 deletions playbooks/infrastructure.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,15 @@

roles:
- role: infrastructure/os_setup
- role: infrastructure/time_setup_ntp
when: ntp_enable | default(False)
- role: infrastructure/time_verify_ntp
when: "'edgenode_group' in group_names"
- role: infrastructure/custom_kernel
when: not (kernel_skip | default(True))
- role: infrastructure/grub

- role: infrastructure/configure_tuned
when: not (tuned_skip | default(False))
- role: infrastructure/conditional_reboot
- role: infrastructure/e810_driver_update
when: e810_driver_enable | default(False)

- role: infrastructure/git_repo
- role: infrastructure/golang
- role: infrastructure/docker
- role: infrastructure/time_setup_ntp
when: ntp_enable | default(False)

- hosts: controller_group
any_errors_fatal: true
Expand All @@ -38,3 +30,15 @@
roles:
- role: infrastructure/ptp/node
when: ptp_sync_enable | default(False)

- hosts: controller_group:edgenode_group
any_errors_fatal: true

roles:
- role: infrastructure/time_verify_ntp
when: "'edgenode_group' in group_names"
- role: infrastructure/e810_driver_update
when: e810_driver_enable | default(False)

- role: infrastructure/golang
- role: infrastructure/docker
2 changes: 1 addition & 1 deletion roles/applications/emco/controlplane/defaults/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
---

_emco:
repo: "https://github.com/open-ness/EMCO"
repo: "https://github.com/smart-edge-open/EMCO"
commit: "{{ emco_commitID }}"
dest: /opt/openness/emco
registryPrefix: "{{ _registry_host }}:{{ _registry_port }}/intel/"
Expand Down
4 changes: 2 additions & 2 deletions roles/infrastructure/docker/files/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ certifi==2020.6.20
cffi==1.14.3
chardet==3.0.4
configobj==4.7.2
cryptography==3.2.1
cryptography==3.3.2
decorator==3.4.0
docker==4.3.1
docker-compose==1.26.2
Expand Down Expand Up @@ -37,6 +37,6 @@ schedutils==0.4
six==1.9.0
texttable==1.6.3
urlgrabber==3.10
urllib3==1.25.11
urllib3==1.26.5
websocket-client==0.56.0
yum-metadata-parser==1.1.4
2 changes: 1 addition & 1 deletion roles/infrastructure/golang/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
with_items:
- "export GOROOT=/usr/local/go"
- "export GOPATH=~/go"
- "export GOPRIVATE=github.com/open-ness"
- "export GOPRIVATE=github.com/smart-edge-open"
- "export PATH=$GOPATH/bin:$GOROOT/bin:$PATH"

- name: Get offline modules
Expand Down
Loading

0 comments on commit ad7b9ba

Please sign in to comment.