Implement nova-ssh container
Add a nova-ssh container to handle the `nova migrate` and `nova resize` case, in which the nova will use ssh to copy files between machines. Change-Id: Ie6675943f3aeabfbba8589d308d55b9c89d732db Closes-Bug: #1562141
This commit is contained in:
parent
808d6baa44
commit
42420830f6
@ -32,6 +32,10 @@ nova_libvirt_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ do
|
|||||||
nova_libvirt_tag: "{{ openstack_release }}"
|
nova_libvirt_tag: "{{ openstack_release }}"
|
||||||
nova_libvirt_image_full: "{{ nova_libvirt_image }}:{{ nova_libvirt_tag }}"
|
nova_libvirt_image_full: "{{ nova_libvirt_image }}:{{ nova_libvirt_tag }}"
|
||||||
|
|
||||||
|
nova_ssh_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ kolla_install_type }}-nova-ssh"
|
||||||
|
nova_ssh_tag: "{{ openstack_release }}"
|
||||||
|
nova_ssh_image_full: "{{ nova_ssh_image }}:{{ nova_ssh_tag }}"
|
||||||
|
|
||||||
nova_conductor_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ kolla_install_type }}-nova-conductor"
|
nova_conductor_image: "{{ docker_registry ~ '/' if docker_registry else '' }}{{ docker_namespace }}/{{ kolla_base_distro }}-{{ kolla_install_type }}-nova-conductor"
|
||||||
nova_conductor_tag: "{{ openstack_release }}"
|
nova_conductor_tag: "{{ openstack_release }}"
|
||||||
nova_conductor_image_full: "{{ nova_conductor_image }}:{{ nova_conductor_tag }}"
|
nova_conductor_image_full: "{{ nova_conductor_image }}:{{ nova_conductor_tag }}"
|
||||||
@ -74,3 +78,5 @@ nova_public_endpoint: "{{ public_protocol }}://{{ kolla_external_fqdn }}:{{ nova
|
|||||||
nova_logging_debug: "{{ openstack_logging_debug }}"
|
nova_logging_debug: "{{ openstack_logging_debug }}"
|
||||||
|
|
||||||
openstack_nova_auth: "{'auth_url':'{{ openstack_auth.auth_url }}','username':'{{ openstack_auth.username }}','password':'{{ openstack_auth.password }}','project_name':'{{ openstack_auth.project_name }}'}"
|
openstack_nova_auth: "{'auth_url':'{{ openstack_auth.auth_url }}','username':'{{ openstack_auth.username }}','password':'{{ openstack_auth.password }}','project_name':'{{ openstack_auth.project_name }}'}"
|
||||||
|
|
||||||
|
nova_ssh_port: "8022"
|
||||||
|
@ -25,6 +25,7 @@
|
|||||||
- "nova-novncproxy"
|
- "nova-novncproxy"
|
||||||
- "nova-scheduler"
|
- "nova-scheduler"
|
||||||
- "nova-spicehtml5proxy"
|
- "nova-spicehtml5proxy"
|
||||||
|
- "nova-ssh"
|
||||||
|
|
||||||
- name: Copying over config.json files for services
|
- name: Copying over config.json files for services
|
||||||
template:
|
template:
|
||||||
@ -40,6 +41,7 @@
|
|||||||
- "nova-novncproxy"
|
- "nova-novncproxy"
|
||||||
- "nova-scheduler"
|
- "nova-scheduler"
|
||||||
- "nova-spicehtml5proxy"
|
- "nova-spicehtml5proxy"
|
||||||
|
- "nova-ssh"
|
||||||
|
|
||||||
- name: Copying over nova.conf
|
- name: Copying over nova.conf
|
||||||
merge_configs:
|
merge_configs:
|
||||||
@ -68,3 +70,13 @@
|
|||||||
template:
|
template:
|
||||||
src: "libvirtd.conf.j2"
|
src: "libvirtd.conf.j2"
|
||||||
dest: "{{ node_config_directory }}/nova-libvirt/libvirtd.conf"
|
dest: "{{ node_config_directory }}/nova-libvirt/libvirtd.conf"
|
||||||
|
|
||||||
|
- name: Copying files for nova-ssh
|
||||||
|
template:
|
||||||
|
src: "{{ item.src }}"
|
||||||
|
dest: "{{ node_config_directory }}/nova-ssh/{{ item.dest }}"
|
||||||
|
with_items:
|
||||||
|
- { src: "sshd_config.j2", dest: "sshd_config" }
|
||||||
|
- { src: "id_rsa", dest: "id_rsa" }
|
||||||
|
- { src: "id_rsa.pub", dest: "id_rsa.pub" }
|
||||||
|
- { src: "ssh_config.j2", dest: "ssh_config" }
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
- name: Ensuring the nova libvirt, conductor, api, consoleauth and scheduler containers are up
|
- name: Ensuring the nova libvirt, ssh, conductor, api, consoleauth and scheduler containers are up
|
||||||
kolla_docker:
|
kolla_docker:
|
||||||
name: "{{ item.name }}"
|
name: "{{ item.name }}"
|
||||||
action: "get_container_state"
|
action: "get_container_state"
|
||||||
@ -8,6 +8,7 @@
|
|||||||
when: inventory_hostname in groups[item.group]
|
when: inventory_hostname in groups[item.group]
|
||||||
with_items:
|
with_items:
|
||||||
- { name: nova_libvirt, group: compute }
|
- { name: nova_libvirt, group: compute }
|
||||||
|
- { name: nova_ssh, group: compute }
|
||||||
- { name: nova_conductor, group: nova-conductor }
|
- { name: nova_conductor, group: nova-conductor }
|
||||||
- { name: nova_api, group: nova-api }
|
- { name: nova_api, group: nova-api }
|
||||||
- { name: nova_consoleauth, group: nova-consoleauth }
|
- { name: nova_consoleauth, group: nova-consoleauth }
|
||||||
@ -55,7 +56,7 @@
|
|||||||
|
|
||||||
- include: config.yml
|
- include: config.yml
|
||||||
|
|
||||||
- name: Check the configs for nova libvirt, conductor, api, consoleauth and scheduler containers
|
- name: Check the configs for nova libvirt, ssh, conductor, api, consoleauth and scheduler containers
|
||||||
command: docker exec {{ item.name }} /usr/local/bin/kolla_set_configs --check
|
command: docker exec {{ item.name }} /usr/local/bin/kolla_set_configs --check
|
||||||
changed_when: false
|
changed_when: false
|
||||||
failed_when: false
|
failed_when: false
|
||||||
@ -63,6 +64,7 @@
|
|||||||
when: inventory_hostname in groups[item.group]
|
when: inventory_hostname in groups[item.group]
|
||||||
with_items:
|
with_items:
|
||||||
- { name: nova_libvirt, group: compute }
|
- { name: nova_libvirt, group: compute }
|
||||||
|
- { name: nova_ssh, group: compute }
|
||||||
- { name: nova_conductor, group: nova-conductor }
|
- { name: nova_conductor, group: nova-conductor }
|
||||||
- { name: nova_api, group: nova-api }
|
- { name: nova_api, group: nova-api }
|
||||||
- { name: nova_consoleauth, group: nova-consoleauth }
|
- { name: nova_consoleauth, group: nova-consoleauth }
|
||||||
@ -107,7 +109,7 @@
|
|||||||
# NOTE(jeffrey4l): when config_strategy == 'COPY_ALWAYS'
|
# NOTE(jeffrey4l): when config_strategy == 'COPY_ALWAYS'
|
||||||
# and container env['KOLLA_CONFIG_STRATEGY'] == 'COPY_ONCE',
|
# and container env['KOLLA_CONFIG_STRATEGY'] == 'COPY_ONCE',
|
||||||
# just remove the container and start again
|
# just remove the container and start again
|
||||||
- name: Containers config strategy for nova libvirt, conductor, api, consoleauth and scheduler containers
|
- name: Containers config strategy for nova libvirt, ssh, conductor, api, consoleauth and scheduler containers
|
||||||
kolla_docker:
|
kolla_docker:
|
||||||
name: "{{ item.name }}"
|
name: "{{ item.name }}"
|
||||||
action: "get_container_env"
|
action: "get_container_env"
|
||||||
@ -115,6 +117,7 @@
|
|||||||
when: inventory_hostname in groups[item.group]
|
when: inventory_hostname in groups[item.group]
|
||||||
with_items:
|
with_items:
|
||||||
- { name: nova_libvirt, group: compute }
|
- { name: nova_libvirt, group: compute }
|
||||||
|
- { name: nova_ssh, group: compute }
|
||||||
- { name: nova_conductor, group: nova-conductor }
|
- { name: nova_conductor, group: nova-conductor }
|
||||||
- { name: nova_api, group: nova-api }
|
- { name: nova_api, group: nova-api }
|
||||||
- { name: nova_consoleauth, group: nova-consoleauth }
|
- { name: nova_consoleauth, group: nova-consoleauth }
|
||||||
@ -156,7 +159,7 @@
|
|||||||
- nova_console == 'spice'
|
- nova_console == 'spice'
|
||||||
- inventory_hostname in groups['nova-spicehtml5proxy']
|
- inventory_hostname in groups['nova-spicehtml5proxy']
|
||||||
|
|
||||||
- name: Remove the nova libvirt, conductor, api, consoleauth and scheduler containers
|
- name: Remove the nova libvirt, ssh, conductor, api, consoleauth and scheduler containers
|
||||||
kolla_docker:
|
kolla_docker:
|
||||||
name: "{{ item[0]['name'] }}"
|
name: "{{ item[0]['name'] }}"
|
||||||
action: "remove_container"
|
action: "remove_container"
|
||||||
@ -167,6 +170,7 @@
|
|||||||
- item[2]['rc'] == 1
|
- item[2]['rc'] == 1
|
||||||
with_together:
|
with_together:
|
||||||
- [{ name: nova_libvirt, group: compute },
|
- [{ name: nova_libvirt, group: compute },
|
||||||
|
{ name: nova_ssh, group: compute },
|
||||||
{ name: nova_conductor, group: nova-conductor },
|
{ name: nova_conductor, group: nova-conductor },
|
||||||
{ name: nova_api, group: nova-api },
|
{ name: nova_api, group: nova-api },
|
||||||
{ name: nova_consoleauth, group: nova-consoleauth },
|
{ name: nova_consoleauth, group: nova-consoleauth },
|
||||||
@ -246,7 +250,7 @@
|
|||||||
- nova_console == 'spice'
|
- nova_console == 'spice'
|
||||||
- remove_nova_spicehtml5proxy_container.changed
|
- remove_nova_spicehtml5proxy_container.changed
|
||||||
|
|
||||||
- name: Restart the nova libvirt, conductor, api, consoleauth and scheduler containers
|
- name: Restart the nova libvirt, ssh, conductor, api, consoleauth and scheduler containers
|
||||||
kolla_docker:
|
kolla_docker:
|
||||||
name: "{{ item[0]['name'] }}"
|
name: "{{ item[0]['name'] }}"
|
||||||
action: "restart_container"
|
action: "restart_container"
|
||||||
@ -257,6 +261,7 @@
|
|||||||
- item[2]['rc'] == 1
|
- item[2]['rc'] == 1
|
||||||
with_together:
|
with_together:
|
||||||
- [{ name: nova_libvirt, group: compute },
|
- [{ name: nova_libvirt, group: compute },
|
||||||
|
{ name: nova_ssh, group: compute },
|
||||||
{ name: nova_conductor, group: nova-conductor },
|
{ name: nova_conductor, group: nova-conductor },
|
||||||
{ name: nova_api, group: nova-api },
|
{ name: nova_api, group: nova-api },
|
||||||
{ name: nova_consoleauth, group: nova-consoleauth },
|
{ name: nova_consoleauth, group: nova-consoleauth },
|
||||||
|
@ -45,6 +45,13 @@
|
|||||||
image: "{{ nova_libvirt_image_full }}"
|
image: "{{ nova_libvirt_image_full }}"
|
||||||
when: inventory_hostname in groups['compute']
|
when: inventory_hostname in groups['compute']
|
||||||
|
|
||||||
|
- name: Pulling nova-ssh image
|
||||||
|
kolla_docker:
|
||||||
|
action: "pull_image"
|
||||||
|
common_options: "{{ docker_common_options }}"
|
||||||
|
image: "{{ nova_ssh_image_full }}"
|
||||||
|
when: inventory_hostname in groups['compute']
|
||||||
|
|
||||||
- name: Pulling nova-novncproxy image
|
- name: Pulling nova-novncproxy image
|
||||||
kolla_docker:
|
kolla_docker:
|
||||||
action: "pull_image"
|
action: "pull_image"
|
||||||
|
@ -64,3 +64,18 @@
|
|||||||
when:
|
when:
|
||||||
- inventory_hostname in groups['compute']
|
- inventory_hostname in groups['compute']
|
||||||
- enable_nova_fake | bool
|
- enable_nova_fake | bool
|
||||||
|
|
||||||
|
- name: Staring nova-ssh container
|
||||||
|
kolla_docker:
|
||||||
|
action: "start_container"
|
||||||
|
common_options: "{{ docker_common_options }}"
|
||||||
|
image: "{{ nova_ssh_image_full }}"
|
||||||
|
name: "nova_ssh"
|
||||||
|
volumes:
|
||||||
|
- "{{ node_config_directory }}/nova-ssh/:{{ container_config_directory }}/:ro"
|
||||||
|
- "kolla_logs:/var/log/kolla"
|
||||||
|
- "nova_compute:/var/lib/nova"
|
||||||
|
- "heka_socket:/var/lib/kolla/heka/"
|
||||||
|
# TODO(jeffrey4l): how to handle the nova-compute-fake and
|
||||||
|
# nova-compute-ironic
|
||||||
|
when: inventory_hostname in groups['compute']
|
||||||
|
1
ansible/roles/nova/templates/id_rsa
Normal file
1
ansible/roles/nova/templates/id_rsa
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{ nova_ssh_key.private_key }}
|
1
ansible/roles/nova/templates/id_rsa.pub
Normal file
1
ansible/roles/nova/templates/id_rsa.pub
Normal file
@ -0,0 +1 @@
|
|||||||
|
{{ nova_ssh_key.public_key }}
|
29
ansible/roles/nova/templates/nova-ssh.json.j2
Normal file
29
ansible/roles/nova/templates/nova-ssh.json.j2
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"command": "/usr/sbin/sshd -D",
|
||||||
|
"config_files": [
|
||||||
|
{
|
||||||
|
"source": "{{ container_config_directory }}/sshd_config",
|
||||||
|
"dest": "/etc/ssh/sshd_config",
|
||||||
|
"owner": "root",
|
||||||
|
"perm": "0644"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "{{ container_config_directory }}/ssh_config",
|
||||||
|
"dest": "/var/lib/nova/.ssh/config",
|
||||||
|
"owner": "nova",
|
||||||
|
"perm": "0600"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "{{ container_config_directory }}/id_rsa",
|
||||||
|
"dest": "/var/lib/nova/.ssh/id_rsa",
|
||||||
|
"owner": "nova",
|
||||||
|
"perm": "0600"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"source": "{{ container_config_directory }}/id_rsa.pub",
|
||||||
|
"dest": "/var/lib/nova/.ssh/authorized_keys",
|
||||||
|
"owner": "nova",
|
||||||
|
"perm": "0600"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
4
ansible/roles/nova/templates/ssh_config.j2
Normal file
4
ansible/roles/nova/templates/ssh_config.j2
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
Host *
|
||||||
|
StrictHostKeyChecking no
|
||||||
|
UserKnownHostsFile /dev/null
|
||||||
|
port {{ nova_ssh_port }}
|
5
ansible/roles/nova/templates/sshd_config.j2
Normal file
5
ansible/roles/nova/templates/sshd_config.j2
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
Port {{ nova_ssh_port }}
|
||||||
|
ListenAddress {{ hostvars[inventory_hostname]['ansible_' + api_interface]['ipv4']['address'] }}
|
||||||
|
|
||||||
|
SyslogFacility AUTHPRIV
|
||||||
|
UsePAM yes
|
@ -45,11 +45,11 @@ RUN apt-get install -y --no-install-recommends \
|
|||||||
|
|
||||||
ADD nova-base-archive /nova-base-source
|
ADD nova-base-archive /nova-base-source
|
||||||
RUN ln -s nova-base-source/* nova \
|
RUN ln -s nova-base-source/* nova \
|
||||||
&& useradd --user-group nova \
|
&& useradd --user-group --home-dir /var/lib/nova nova \
|
||||||
&& /var/lib/kolla/venv/bin/pip --no-cache-dir install --upgrade -c requirements/upper-constraints.txt /nova \
|
&& /var/lib/kolla/venv/bin/pip --no-cache-dir install --upgrade -c requirements/upper-constraints.txt /nova \
|
||||||
&& mkdir -p /etc/nova /home/nova /var/lib/nova \
|
&& mkdir -p /etc/nova /var/lib/nova \
|
||||||
&& cp -r /nova/etc/nova/* /etc/nova/ \
|
&& cp -r /nova/etc/nova/* /etc/nova/ \
|
||||||
&& chown -R nova: /etc/nova /home/nova /var/lib/nova \
|
&& chown -R nova: /etc/nova /var/lib/nova \
|
||||||
&& sed -i 's|^exec_dirs.*|exec_dirs=/var/lib/kolla/venv/bin,/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin|g' /etc/nova/rootwrap.conf
|
&& sed -i 's|^exec_dirs.*|exec_dirs=/var/lib/kolla/venv/bin,/sbin,/usr/sbin,/bin,/usr/bin,/usr/local/bin,/usr/local/sbin|g' /etc/nova/rootwrap.conf
|
||||||
|
|
||||||
COPY nova_sudoers /etc/sudoers.d/nova_sudoers
|
COPY nova_sudoers /etc/sudoers.d/nova_sudoers
|
||||||
|
23
docker/nova/nova-ssh/Dockerfile.j2
Normal file
23
docker/nova/nova-ssh/Dockerfile.j2
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
FROM {{ namespace }}/{{ image_prefix }}nova-base:{{ tag }}
|
||||||
|
MAINTAINER {{ maintainer }}
|
||||||
|
|
||||||
|
{% if base_distro in ['centos', 'fedora', 'oraclelinux', 'rhel'] %}
|
||||||
|
|
||||||
|
RUN yum -y install \
|
||||||
|
openssh-server \
|
||||||
|
&& yum clean all
|
||||||
|
|
||||||
|
{% elif base_distro in ['ubuntu', 'debian'] %}
|
||||||
|
|
||||||
|
RUN apt-get install -y --no-install-recommends \
|
||||||
|
openssh-server \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& mkdir -p /var/run/sshd \
|
||||||
|
&& chmod 0755 /var/run/sshd
|
||||||
|
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
COPY extend_start.sh /usr/local/bin/kolla_extend_start
|
||||||
|
RUN chmod 755 /usr/local/bin/kolla_extend_start
|
||||||
|
|
||||||
|
{{ include_footer }}
|
20
docker/nova/nova-ssh/extend_start.sh
Normal file
20
docker/nova/nova-ssh/extend_start.sh
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
if [[ ! -L /dev/log ]]; then
|
||||||
|
ln -sf /var/lib/kolla/heka/log /dev/log
|
||||||
|
fi
|
||||||
|
|
||||||
|
SSH_HOST_KEY_TYPES=( "rsa" "dsa" "ecdsa" "ed25519" )
|
||||||
|
|
||||||
|
for key_type in ${SSH_HOST_KEY_TYPES[@]}; do
|
||||||
|
KEY_PATH=/etc/ssh/ssh_host_${key_type}_key
|
||||||
|
if [[ ! -f "${KEY_PATH}" ]]; then
|
||||||
|
ssh-keygen -q -t ${key_type} -f ${KEY_PATH} -N ""
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
mkdir -p /var/lib/nova/.ssh
|
||||||
|
|
||||||
|
if [[ $(stat -c %U:%G /var/lib/nova/.ssh) != "nova:nova" ]]; then
|
||||||
|
chown nova: /var/lib/nova/.ssh
|
||||||
|
fi
|
@ -65,6 +65,13 @@ manila_keystone_password:
|
|||||||
|
|
||||||
memcache_secret_key:
|
memcache_secret_key:
|
||||||
|
|
||||||
|
nova_ssh_private_key:
|
||||||
|
nova_ssh_public_key:
|
||||||
|
|
||||||
|
nova_ssh_key:
|
||||||
|
private_key:
|
||||||
|
public_key:
|
||||||
|
|
||||||
####################
|
####################
|
||||||
# RabbitMQ options
|
# RabbitMQ options
|
||||||
####################
|
####################
|
||||||
|
@ -12,16 +12,29 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
|
import os
|
||||||
import random
|
import random
|
||||||
import string
|
import string
|
||||||
import uuid
|
import uuid
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
|
from Crypto.PublicKey import RSA
|
||||||
|
|
||||||
|
|
||||||
|
def generate_RSA(bits=2048):
|
||||||
|
new_key = RSA.generate(bits, os.urandom)
|
||||||
|
private_key = new_key.exportKey("PEM")
|
||||||
|
public_key = new_key.publickey().exportKey("OpenSSH")
|
||||||
|
return private_key, public_key
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
# These keys should be random uuids
|
# These keys should be random uuids
|
||||||
uuid_keys = ['ceph_cluster_fsid', 'rbd_secret_uuid']
|
uuid_keys = ['ceph_cluster_fsid', 'rbd_secret_uuid']
|
||||||
|
|
||||||
|
# SSH key pair
|
||||||
|
ssh_keys = ['nova_ssh_key']
|
||||||
|
|
||||||
# If these keys are None, leave them as None
|
# If these keys are None, leave them as None
|
||||||
blank_keys = ['docker_registry_password']
|
blank_keys = ['docker_registry_password']
|
||||||
|
|
||||||
@ -32,6 +45,16 @@ def main():
|
|||||||
passwords = yaml.load(f.read())
|
passwords = yaml.load(f.read())
|
||||||
|
|
||||||
for k, v in passwords.items():
|
for k, v in passwords.items():
|
||||||
|
if (k in ssh_keys and
|
||||||
|
(v is None
|
||||||
|
or v.get('public_key') is None
|
||||||
|
and v.get('private_key') is None)):
|
||||||
|
private_key, public_key = generate_RSA()
|
||||||
|
passwords[k] = {
|
||||||
|
'private_key': private_key,
|
||||||
|
'public_key': public_key
|
||||||
|
}
|
||||||
|
continue
|
||||||
if v is None:
|
if v is None:
|
||||||
if k in blank_keys and v is None:
|
if k in blank_keys and v is None:
|
||||||
continue
|
continue
|
||||||
|
@ -11,3 +11,4 @@ oslo.config>=3.7.0 # Apache-2.0
|
|||||||
graphviz>=0.4.0 # MIT License
|
graphviz>=0.4.0 # MIT License
|
||||||
beautifulsoup4 # MIT
|
beautifulsoup4 # MIT
|
||||||
setuptools>=16.0 # PSF/ZPL
|
setuptools>=16.0 # PSF/ZPL
|
||||||
|
pycrypto>=2.6 # Public Domain
|
||||||
|
Loading…
Reference in New Issue
Block a user