From 252ab4f6a44c0d64b1e4cfb2b86e9bf58b5d9ffb Mon Sep 17 00:00:00 2001 From: deepssin Date: Mon, 16 Jun 2025 09:55:21 -0400 Subject: [PATCH] Revive teuthology-openstack Signed-off-by: deepssin --- bootstrap | 20 +- requirements.txt | 4 +- setup.cfg | 1 + teuthology/openstack/__init__.py | 374 +++++++++++++----------- teuthology/openstack/setup-openstack.sh | 79 +++-- teuthology/suite/run.py | 4 +- 6 files changed, 275 insertions(+), 207 deletions(-) diff --git a/bootstrap b/bootstrap index 5f9085e824..6e6bfd0526 100755 --- a/bootstrap +++ b/bootstrap @@ -26,6 +26,7 @@ if [ -z "$PYTHON" ]; then # This would be bizarre, but I suppose possible PYTHON=${PYTHON:-"python3"} fi +echo "Using python: $PYTHON" case "$(uname -s)" in Linux) @@ -40,8 +41,8 @@ Linux) install_pkg="sudo dnf install -y" case "$OS" in Ubuntu|Debian|LinuxMint) - deps=(qemu-utils python3-dev libssl-dev python3-pip python3-wheel $PYTHON-venv libev-dev libvirt-dev libffi-dev libyaml-dev) - has_pkg="dpkg -C" + deps=(qemu-utils python3-dev libssl-dev python3-pip python3-wheel python3-venv libev-dev libvirt-dev libffi-dev libyaml-dev build-essential jq curl) + has_pkg="dpkg -s" install_pkg="sudo apt install -y" ;; RedHatEnterpriseWorkstation|RedHatEnterpriseServer|RedHatEnterprise|CentOS) @@ -114,17 +115,26 @@ if [ -z "$NO_CLOBBER" ] && \ [ ! -e "$VENV/bin/pip" -o ! -e "$VENV/bin/$PYTHON" ] || \ [ "${PYTHON_VER_OUT}" != "$($VENV/bin/$PYTHON --version)" ] \ ; then + echo "Deleting existing virtual environment" rm -rf virtualenv fi if [ -z "$NO_CLOBBER" ] || [ ! -e $VENV ]; then + echo "Creating new venv at $VENV" $PYTHON_BIN -m venv $VENV fi +PY_MAJOR=$($VENV/bin/python -c "import sys; print(sys.version_info[0])") +PY_MINOR=$($VENV/bin/python -c "import sys; print(sys.version_info[1])") + +# Downgrade pip for older python versions +if [ "$PY_MAJOR" -eq 3 ] && [ "$PY_MINOR" -lt 6 ]; then + $VENV/bin/python -m ensurepip + $VENV/bin/pip install pip==20.3.4 --force-reinstall +fi + $VENV/bin/pip install packaging -# It is impossible to upgrade ansible from 2.9 to 2.10 via pip. -# See https://docs.ansible.com/ansible/devel/porting_guides/porting_guide_2.10.html#known-issues if [ -f "$VENV/bin/ansible" ]; then uninstall_ansible=$($VENV/bin/python3 -c "import ansible; from packaging.version import parse; print(parse(ansible.__version__) < parse('2.10.0'))") if [ "$uninstall_ansible" = "True" ]; then @@ -146,3 +156,5 @@ $VENV/bin/pip check # Install ansible collections $VENV/bin/ansible-galaxy install -r requirements.yml + +echo "Bootstrap completed successfully!!!" diff --git a/requirements.txt b/requirements.txt index c4a05b2f4d..a5245266c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -200,7 +200,9 @@ zope-event==5.0 # via gevent zope-interface==6.1 # via gevent - +openstacksdk==4.5.0 + # via teuthology (pyproject.toml) +python-openstackclient>=6.0.0 # The following packages are considered to be unsafe in a requirements file: # pip # setuptools diff --git a/setup.cfg b/setup.cfg index a18d92bb7e..c03da52ad7 100644 --- a/setup.cfg +++ b/setup.cfg @@ -110,6 +110,7 @@ test = openstack = python-openstackclient python-novaclient + openstacksdk [options.package_data] teuthology.openstack = diff --git a/teuthology/openstack/__init__.py b/teuthology/openstack/__init__.py index 0f626e55c9..1fb5481917 100644 --- a/teuthology/openstack/__init__.py +++ b/teuthology/openstack/__init__.py @@ -46,6 +46,7 @@ from teuthology.config import config as teuth_config from teuthology.config import set_config_attr from teuthology.orchestra import connection from teuthology import misc +from openstack import connection as openstack_connection from yaml.representer import SafeRepresenter @@ -78,6 +79,8 @@ class OpenStackInstance(object): self.name_or_id = name_or_id self.private_or_floating_ip = None self.private_ip = None + self.info = info + self.conn = self._create_connection() if info is None: self.set_info() else: @@ -88,11 +91,14 @@ class OpenStackInstance(object): errmsg = '{}: {}'.format(errmsg, self.info['message']) raise Exception(errmsg) + def _create_connection(self): + return openstack_connection.from_config(cloud=None) + def set_info(self): try: - self.info = json.loads( - OpenStack().run("server show -f json " + self.name_or_id)) - enforce_json_dictionary(self.info) + server = self.conn.compute.find_server(self.name_or_id) + if server: + self.info = {k.lower(): v for k, v in server.to_dict().items()} except CalledProcessError: self.info = None @@ -129,32 +135,17 @@ class OpenStackInstance(object): self.set_info() def get_ip_neutron(self): - subnets = json.loads(misc.sh("unset OS_AUTH_TYPE OS_TOKEN ; " - "neutron subnet-list -f json -c id -c ip_version")) - subnet_ids = [] - for subnet in subnets: - if subnet['ip_version'] == 4: - subnet_ids.append(subnet['id']) - if not subnet_ids: - raise Exception("no subnet with ip_version == 4") - ports = json.loads(misc.sh("unset OS_AUTH_TYPE OS_TOKEN ; " - "neutron port-list -f json -c fixed_ips -c device_id")) - fixed_ips = None + conn = OpenStack().conn + subnets = [subnet.id for subnet in conn.network.subnets() if subnet.ip_version == 4] + if not subnets: + raise Exception("No subnet with ip_version == 4 found") + ports = conn.network.ports(device_id=self['id']) for port in ports: - if port['device_id'] == self['id']: - fixed_ips = port['fixed_ips'].split("\n") - break - if not fixed_ips: - raise Exception("no fixed ip record found") - ip = None - for fixed_ip in fixed_ips: - record = json.loads(fixed_ip) - if record['subnet_id'] in subnet_ids: - ip = record['ip_address'] - break - if not ip: - raise Exception("no ip") - return ip + for fixed_ip in port.fixed_ips: + if fixed_ip.get('subnet_id') in subnets: + return fixed_ip['ip_address'] + + raise Exception("No IP found for instance") def get_ip(self, network): """ @@ -221,6 +212,14 @@ class OpenStack(object): 'ubuntu-16.04-x86_64': 'https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-amd64-disk1.img', 'ubuntu-16.04-aarch64': 'https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-arm64-disk1.img', 'ubuntu-16.04-i686': 'https://cloud-images.ubuntu.com/xenial/current/xenial-server-cloudimg-i386-disk1.img', + 'ubuntu-18.04-x86_64': 'https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-amd64.img', + 'ubuntu-18.04-aarch64': 'https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-arm64.img', + 'ubuntu-18.04-i686': 'https://cloud-images.ubuntu.com/bionic/current/bionic-server-cloudimg-i386.img', + 'ubuntu-20.04-x86_64': 'https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-amd64.img', + 'ubuntu-20.04-aarch64': 'https://cloud-images.ubuntu.com/focal/current/focal-server-cloudimg-arm64.img', + 'ubuntu-22.04-x86_64': 'https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-amd64.img', + 'ubuntu-22.04-aarch64': 'https://cloud-images.ubuntu.com/jammy/current/jammy-server-cloudimg-arm64.img', + 'ubuntu-24.04-x86_64': 'https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img', 'debian-8.0-x86_64': 'http://cdimage.debian.org/cdimage/openstack/current/debian-8.7.1-20170215-openstack-amd64.qcow2', } @@ -230,11 +229,15 @@ class OpenStack(object): self.username = 'ubuntu' self.up_string = "UNKNOWN" self.teuthology_suite = 'teuthology-suite' + self.conn = self._create_connection() token = None token_expires = None token_cache_duration = 3600 + def _create_connection(self): + return openstack_connection.from_config(cloud=None) + def cache_token(self): if self.provider != 'ovh': return False @@ -348,9 +351,10 @@ class OpenStack(object): """ Return the uuid of the network in OpenStack. """ - r = json.loads(self.run("network show -f json " + - network)) - return self.get_value(r, 'id') + conn = self.conn + network = conn.network.find_network(network) + if network: + return network.id def type_version_arch(self, os_type, os_version, arch): """ @@ -409,11 +413,10 @@ class OpenStack(object): @staticmethod def sort_flavors(flavors): - def sort_flavor(a, b): - return (a['VCPUs'] - b['VCPUs'] or - a['RAM'] - b['RAM'] or - a['Disk'] - b['Disk']) - return sorted(flavors, cmp=sort_flavor) + def sort_key(flavor): + # Create a tuple for sorting: (VCPUs, RAM, Disk) + return (flavor['VCPUs'], flavor['RAM'], flavor['Disk']) + return sorted(flavors, key=sort_key) def get_os_flavors(self): flavors = json.loads(self.run("flavor list -f json")) @@ -533,19 +536,21 @@ class OpenStack(object): @staticmethod def list_instances(): + conn = OpenStack().conn ownedby = "ownedby='" + teuth_config.openstack['ip'] + "'" - all = json.loads(OpenStack().run( - "server list -f json --long --name 'target'")) - return filter(lambda instance: ownedby in instance['Properties'], all) + instances = conn.compute.servers(all_projects=True) + return [inst for inst in instances if ownedby in (getattr(inst, 'metadata', {}) or {}).get('Properties', '')] @staticmethod def list_volumes(): + conn = OpenStack().conn ownedby = "ownedby='" + teuth_config.openstack['ip'] + "'" - all = json.loads(OpenStack().run("volume list -f json --long")) + volumes = conn.block_storage.volumes() def select(volume): - return (ownedby in volume['Properties'] and - volume['Display Name'].startswith('target')) - return filter(select, all) + props = volume.metadata or {} + return (ownedby in props.get('Properties', '') and + props.get('display_name', '').startswith('target')) + return filter(select, volumes) def cloud_init_wait(self, instance): """ @@ -564,12 +569,11 @@ class OpenStack(object): with safe_while(sleep=30, tries=30, action="cloud_init_wait " + ip) as proceed: success = False - # CentOS 6.6 logs in /var/log/clout-init-output.log - # CentOS 7.0 logs in /var/log/clout-init.log tail = ("tail --follow=name --retry" " /var/log/cloud-init*.log /tmp/init.out") while proceed(): try: + log.debug("Attempting to connect to instance at IP: " + ip) client = connection.connect(**client_args) except paramiko.PasswordRequiredException: raise Exception( @@ -611,15 +615,17 @@ class OpenStack(object): break except socket.timeout: client.close() - log.debug('cloud_init_wait socket.timeout ' + tail) continue - except socket.error as e: + except socket.error: client.close() - log.debug('cloud_init_wait socket.error ' + str(e) + ' ' + tail) continue - client.close() + finally: + client.close() if success: + log.debug('Cloud-init completed successfully for IP: ' + ip) break + if not success: + log.debug('Cloud-init did not complete successfully within the given retries.') return success def get_ip(self, instance_id, network): @@ -899,7 +905,7 @@ class TeuthologyOpenStack(OpenStack): if ceph_repo: command = ( "perl -pi -e 's|.*{opt}.*|{opt}: {value}|'" - " ~/.teuthology.yaml" + " ~/.teuthology.yaml || true" ).format(opt='ceph_git_url', value=ceph_repo) self.ssh(command) user_home = '/home/' + self.username @@ -914,6 +920,7 @@ class TeuthologyOpenStack(OpenStack): " --machine-type openstack " + " ".join(map(lambda x: "'" + x + "'", argv)) ) + log.info("Running teuthology-suite: " + command) return self.ssh(command) def reminders(self): @@ -951,7 +958,7 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/ logging.getLogger("paramiko.transport").setLevel(logging.DEBUG) teuthology.log.setLevel(loglevel) - def ssh(self, command): + def ssh(self, command, timeout=300): """ Run a command in the OpenStack instance of the teuthology cluster. Return the stdout / stderr of the command. @@ -969,14 +976,27 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/ # get the I/O channel to iterate line by line transport = client.get_transport() channel = transport.open_session() - channel.get_pty() - channel.settimeout(900) - output = channel.makefile('r', 1) - log.debug(":ssh@" + ip + ":" + command) + channel.settimeout(timeout) + log.debug(f"ssh {self.instance.get_floating_ip_or_ip()}: {command}") channel.exec_command(command) - for line in iter(output.readline, b''): - log.info(line.strip()) - return channel.recv_exit_status() + stdout, stderr = [], [] + start_time = time.time() + while True: + if channel.recv_ready(): + stdout.append(channel.recv(4096).decode()) + if channel.recv_stderr_ready(): + stderr.append(channel.recv_stderr(4096).decode()) + if channel.exit_status_ready(): + break + if time.time() - start_time > timeout: + raise TimeoutError("SSH command timed out!") + time.sleep(0.1) # Small sleep to avoid busy waiting + exit_status = channel.recv_exit_status() + stdout_txt, stderr_txt = ''.join(stdout), ''.join(stderr) + if exit_status != 0: + log.warning(f"SSH command failed with exit status {exit_status}") + return exit_status, stdout_txt, stderr_txt + def verify_openstack(self): """ @@ -1029,32 +1049,48 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/ fd, path = tempfile.mkstemp() os.close(fd) - with open(os.path.dirname(__file__) + '/bootstrap-teuthology.sh', 'rb') as f: + bootstrap_path = os.getcwd() + "/teuthology/openstack" + '/bootstrap-teuthology.sh' + with open(bootstrap_path, 'rb') as f: b64_bootstrap = base64.b64encode(f.read()) bootstrap_content = str(b64_bootstrap.decode()) openrc_sh = '' cacert_cmd = None - for (var, value) in os.environ.items(): - if var in ('OS_TOKEN_VALUE', 'OS_TOKEN_EXPIRES'): - continue - if var == 'OS_CACERT': - cacert_path = '/home/%s/.openstack.crt' % self.username - cacert_file = value - openrc_sh += 'export %s=%s\n' % (var, cacert_path) - cacert_cmd = ( - "su - -c 'cat > {path}' {user} < {path}' {user} <> /tmp/init.out " "2>&1".format(user=self.username, opts=' '.join(setup_options + all_options))), - # wa: we want to stop paddles and pulpito started by - # setup-openstack before starting teuthology service "pkill -f 'pecan serve'", "pkill -f 'python run.py'", "systemctl enable teuthology", @@ -1143,11 +1175,6 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/ 'name': self.username } }, - 'packages': [ - 'python-virtualenv', - 'git', - 'rsync', - ], 'write_files': [ { 'path': '/tmp/bootstrap-teuthology.sh', @@ -1163,7 +1190,9 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/ 'permissions': '0644', } ], - 'runcmd': cmds, + 'runcmd': [ + 'apt-get update && apt-get install -y python3-virtualenv git rsync >> /tmp/init.out 2>&1' + ] + cmds, 'final_message': 'teuthology is up and running after $UPTIME seconds' } user_data = "#cloud-config\n%s" % \ @@ -1186,36 +1215,41 @@ ssh access : ssh {identity}{username}@{ip} # logs in /usr/share/nginx/ return "teuth-%s-worker" % self.args.name def create_security_group(self): - """ - Create a security group that will be used by all teuthology - created instances. This should not be necessary in most cases - but some OpenStack providers enforce firewall restrictions even - among instances created within the same tenant. - """ - groups = misc.sh('openstack security group list -c Name -f value').split('\n') - if all(g in groups for g in [self.server_group(), self.worker_group()]): - return - misc.sh(""" -openstack security group delete {server} || true -openstack security group delete {worker} || true -openstack security group create {server} -openstack security group create {worker} -# access to teuthology VM from the outside -openstack security group rule create --proto tcp --dst-port 22 {server} # ssh -openstack security group rule create --proto tcp --dst-port 80 {server} # for log access -openstack security group rule create --proto tcp --dst-port 8080 {server} # pulpito -openstack security group rule create --proto tcp --dst-port 8081 {server} # paddles -# access between teuthology and workers -openstack security group rule create --src-group {worker} --dst-port 1:65535 {server} -openstack security group rule create --protocol udp --src-group {worker} --dst-port 1:65535 {server} -openstack security group rule create --src-group {server} --dst-port 1:65535 {worker} -openstack security group rule create --protocol udp --src-group {server} --dst-port 1:65535 {worker} -# access between members of one group -openstack security group rule create --src-group {worker} --dst-port 1:65535 {worker} -openstack security group rule create --protocol udp --src-group {worker} --dst-port 1:65535 {worker} -openstack security group rule create --src-group {server} --dst-port 1:65535 {server} -openstack security group rule create --protocol udp --src-group {server} --dst-port 1:65535 {server} - """.format(server=self.server_group(), worker=self.worker_group())) + conn = OpenStack().conn + server_sg = conn.network.find_security_group(self.server_group()) + worker_sg = conn.network.find_security_group(self.worker_group()) + if not server_sg: + server_sg = conn.network.create_security_group(name=self.server_group()) + if not worker_sg: + worker_sg = conn.network.create_security_group(name=self.worker_group()) + def add_rule(sg_id, protocol, port, remote_group_id=None): + rule_args = { + 'security_group_id': sg_id, + 'direction': 'ingress', + 'protocol': protocol, + 'port_range_min': port, + 'port_range_max': port, + 'ethertype': 'IPv4', + } + if remote_group_id: + rule_args['remote_group_id'] = remote_group_id + else: + rule_args['remote_ip_prefix'] = '0.0.0.0/0' + try: + conn.network.create_security_group_rule(**rule_args) + except Exception as e: + log.warning(f"Security group rule creation skipped or failed: {e}") + # Rules for SSH, log, pulpito and paddles + for port in (22, 80, 8080, 8081): + add_rule(server_sg.id, 'tcp', port) + # access between teuthology and workers + for port in (65535,): + add_rule(worker_sg.id, 'udp', port, remote_group_id=server_sg.id) + add_rule(server_sg.id, 'udp', port, remote_group_id=worker_sg.id) + # access between members of one group + add_rule(server_sg.id, 'udp', 65535, remote_group_id=server_sg.id) + # access within worker group + add_rule(worker_sg.id, 'udp', 65535, remote_group_id=worker_sg.id) @staticmethod def get_unassociated_floating_ip(): @@ -1230,32 +1264,14 @@ openstack security group rule create --protocol udp --src-group {server} --dst-p @staticmethod def create_floating_ip(): - try: - pools = json.loads(OpenStack().run("ip floating pool list -f json")) - except subprocess.CalledProcessError as e: - if 'Floating ip pool operations are only available for Compute v2 network.' \ - in e.output: - log.debug(e.output) - log.debug('Trying newer API than Compute v2') - try: - network = 'floating' - ip = json.loads(misc.sh("openstack --quiet floating ip create -f json '%s'" % network)) - return ip['floating_ip_address'] - except subprocess.CalledProcessError: - log.debug("Can't create floating ip for network '%s'" % network) - - log.debug("create_floating_ip: ip floating pool list failed") - return None - if not pools: + conn = OpenStack().conn + network_name = 'floating' + network = conn.network.find_network(network_name) + if not network: + log.debug(f"Floating network {network_name} not found.") return None - pool = pools[0]['Name'] - try: - ip = json.loads(OpenStack().run( - "ip floating create -f json '" + pool + "'")) - return ip['ip'] - except subprocess.CalledProcessError: - log.debug("create_floating_ip: not creating a floating ip") - return None + floating_ip = conn.network.create_ip(floating_network_id=network.id) + return floating_ip.floating_ip_address @staticmethod def associate_floating_ip(name_or_id): @@ -1263,23 +1279,18 @@ openstack security group rule create --protocol udp --src-group {server} --dst-p Associate a floating IP to the OpenStack instance or do nothing if no floating ip can be created. """ - ip = TeuthologyOpenStack.get_unassociated_floating_ip() - if not ip: - ip = TeuthologyOpenStack.create_floating_ip() - if ip: - OpenStack().run("ip floating add " + ip + " " + name_or_id) + conn = OpenStack().conn + server = conn.compute.find_server(name_or_id) + ip_address = TeuthologyOpenStack.get_unassociated_floating_ip() + if not ip_address: + ip_address = TeuthologyOpenStack.create_floating_ip() + if ip_address: + conn.compute.add_floating_ip_to_server(server, ip_address) @staticmethod def get_os_floating_ips(): - try: - ips = json.loads(OpenStack().run("ip floating list -f json")) - except subprocess.CalledProcessError as e: - log.warning(e) - if e.returncode == 1: - return [] - else: - raise e - return ips + conn = OpenStack().conn + return list(conn.network.ips()) @staticmethod def get_floating_ip_id(ip): @@ -1307,12 +1318,17 @@ openstack security group rule create --protocol udp --src-group {server} --dst-p """ Remove the floating ip from instance_id and delete it. """ - ip = OpenStackInstance(instance_id).get_floating_ip() - if not ip: + conn = OpenStack().conn + server = conn.compute.find_server(instance_id) + if not server: + return + ip_address = OpenStackInstance(instance_id).get_floating_ip() + if not ip_address: return - OpenStack().run("ip floating remove " + ip + " " + instance_id) - ip_id = TeuthologyOpenStack.get_floating_ip_id(ip) - OpenStack().run("ip floating delete " + ip_id) + conn.compute.remove_floating_ip_from_server(server, ip_address) + floating_ip_obj = conn.network.find_ip(ip_address) + if floating_ip_obj: + conn.network.delete_ip(floating_ip_obj) def create_cluster(self): user_data = self.get_user_data() @@ -1329,19 +1345,29 @@ openstack security group rule create --protocol udp --src-group {server} --dst-p if not key_name: raise Exception('No key name provided, use --key-name option') log.debug('Using key name: %s' % self.args.key_name) - self.run( + image_name = self.image('ubuntu', '22.04', arch) + log.debug("Using image: %s" % image_name) + net_config = self.net() + try: + self.run( "server create " + - " --image '" + self.image('ubuntu', '16.04', arch) + "' " + + " --image '" + image_name + "' " + " --flavor '" + flavor + "' " + - " " + self.net() + + " " + net_config + " --key-name " + key_name + " --user-data " + user_data + security_group + " --wait " + self.server_name() + " -f json") - os.unlink(user_data) + except Exception as e: + log.error("Error during server creation: %s" % str(e)) + raise + finally: + os.unlink(user_data) self.instance = OpenStackInstance(self.server_name()) + log.debug("OpenStackInstance created for server: %s" % self.server_name()) self.associate_floating_ip(self.instance['id']) + log.debug("Floating IP associated for instance ID: %s" % self.instance.get('id')) return self.cloud_init_wait(self.instance) def packages_repository(self): diff --git a/teuthology/openstack/setup-openstack.sh b/teuthology/openstack/setup-openstack.sh index b6d9aa1b09..526ba98136 100755 --- a/teuthology/openstack/setup-openstack.sh +++ b/teuthology/openstack/setup-openstack.sh @@ -41,6 +41,8 @@ function create_config() { local server_group="${11}" local worker_group="${12}" local package_repo="${13}" + local teuthology_branch="${14}" + local teuthology_git_url="${15}" if test "$network" ; then network="network: $network" @@ -65,6 +67,8 @@ queue_host: localhost lab_domain: $labdomain max_job_time: 32400 # 9 hours teuthology_path: . +teuthology_branch: $teuthology_branch +teuthology_git_url: $teuthology_git_url canonical_tags: $canonical_tags openstack: clone: git clone http://github.com/ceph/teuthology @@ -113,13 +117,23 @@ function apt_get_update() { } function setup_docker() { - if test -f /etc/apt/sources.list.d/docker.list ; then - echo "OK docker is installed" - else - sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D - echo deb https://apt.dockerproject.org/repo ubuntu-trusty main | sudo tee -a /etc/apt/sources.list.d/docker.list - sudo apt-get -qq install -y docker-engine + source /etc/os-release + if ! $VERSION_CODENAME; then + echo "ERROR: VERSION_CODENAME is not set. Cannot proceed with Docker installation." + return + fi + if !command -v docker &> /dev/null; then + curl -fsSL https://download.docker.com/linux/ubuntu/gpg | \ + sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg + echo \ + "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] \ + https://download.docker.com/linux/ubuntu $VERSION_CODENAME stable" | \ + sudo tee /etc/apt/sources.list.d/docker.list > /dev/null + sudo apt-get update + sudo apt-get install -y docker-ce docker-ce-cli containerd.io echo "INSTALLED docker" + else + echo "OK docker is installed" fi } @@ -163,7 +177,7 @@ function setup_paddles() { git clone https://github.com/ceph/paddles.git $paddles_dir || return 1 fi - sudo apt-get -qq install -y --force-yes beanstalkd postgresql postgresql-contrib postgresql-server-dev-all supervisor + sudo apt-get -qq install -y beanstalkd postgresql postgresql-contrib postgresql-server-dev-all supervisor if ! sudo /etc/init.d/postgresql status ; then sudo mkdir -p /etc/postgresql @@ -179,10 +193,10 @@ function setup_paddles() { cd $paddles_dir || return 1 git pull --rebase git clean -ffqdx - sed -e "s|^address.*|address = 'http://localhost'|" \ - -e "s|^job_log_href_templ = 'http://qa-proxy.ceph.com/teuthology|job_log_href_templ = 'http://$public_ip|" \ + sed -e "/^address = os.environ.get(/,/^)/c\address = os.environ.get('PADDLES_ADDRESS', 'http://localhost')" \ + -e "s|^job_log_href_templ = os.environ.get(.*)|job_log_href_templ = os.environ.get('PADDLES_JOB_LOG_HREF_TEMPL', 'http://$public_ip')|" \ -e "/sqlite/d" \ - -e "s|.*'postgresql+psycop.*'|'url': 'postgresql://paddles:paddles@localhost/paddles'|" \ + -e "s|^ *'url':.*|'url': 'postgresql://paddles:paddles@localhost/paddles',|" \ -e "s/'host': '127.0.0.1'/'host': '0.0.0.0'/" \ < config.py.in > config.py virtualenv ./virtualenv @@ -258,7 +272,7 @@ function setup_pulpito() { git clone https://github.com/ceph/pulpito.git $pulpito_dir || return 1 fi - sudo apt-get -qq install -y --force-yes nginx + sudo apt-get -qq install -y nginx local nginx_conf=/etc/nginx/sites-available/default sudo sed -i '/text\/plain/a\ text\/plain log;' \ /etc/nginx/mime.types @@ -278,7 +292,7 @@ function setup_pulpito() { virtualenv ./virtualenv source ./virtualenv/bin/activate pip install --upgrade pip - pip install 'setuptools==18.2.0' + pip install 'setuptools>=58.0.0' pip install -r requirements.txt python run.py & ) @@ -408,7 +422,7 @@ function setup_dnsmasq() { if ! test -f /etc/dnsmasq.d/resolv ; then resolver=$(grep nameserver /etc/resolv.conf | head -1 | perl -ne 'print $1 if(/\s*nameserver\s+([\d\.]+)/)') - sudo apt-get -qq install -y --force-yes dnsmasq resolvconf + sudo apt-get -qq install -y dnsmasq resolvconf # FIXME: this opens up dnsmasq to DNS reflection/amplification attacks, and can be reverted # FIXME: once we figure out how to configure dnsmasq to accept DNS queries from all subnets sudo perl -pi -e 's/--local-service//' /etc/init.d/dnsmasq @@ -500,22 +514,27 @@ function remove_images() { } function install_packages() { - - if ! test -f /etc/apt/sources.list.d/trusty-backports.list ; then - echo deb http://archive.ubuntu.com/ubuntu trusty-backports main universe | sudo tee /etc/apt/sources.list.d/trusty-backports.list + source /etc/os-release + if ! $VERSION_CODENAME; then + echo "ERROR: VERSION_CODENAME is not set. Cannot proceed with Docker installation." + return + fi + local codename=$VERSION_CODENAME + local backports_file="/etc/apt/sources.list.d/${codename}-backports.list" + if [ ! -f "$backports_file" ]; then + echo "Adding backports repo for $codename..." + echo "deb http://archive.ubuntu.com/ubuntu ${codename}-backports main universe" | sudo tee "$backports_file" sudo apt-get update fi - - local packages="jq realpath curl" - sudo apt-get -qq install -y --force-yes $packages - + local packages="jq curl" + sudo apt-get -qq install -y $packages echo "INSTALL required packages $packages" } CAT=${CAT:-cat} function verify_openstack() { - if ! openstack server list > /dev/null ; then + if ! openstack server list --format json > /dev/null ; then echo ERROR: the credentials from ~/openrc.sh are not working >&2 return 1 fi @@ -701,13 +720,14 @@ function main() { # assume the first available IPv4 subnet is going to be used to assign IP to the instance # [ -z "$network" ] && { - local default_subnets=$(openstack subnet list --ip-version 4 -f json | jq -r '.[] | .Subnet' | sort | uniq) + local default_subnets=$(openstack subnet list --ip-version 4 -f json | jq -r '.[] | select(.Name != null) | .Subnet' | sort | uniq) } || { local network_id=$(openstack network list -f json | jq -r ".[] | select(.Name == \"$network\") | .ID") local default_subnets=$(openstack subnet list --ip-version 4 -f json \ | jq -r ".[] | select(.Network == \"$network_id\") | .Subnet" | sort | uniq) } subnets=$(echo $subnets $default_subnets) + echo "subnets: $subnets" case $provider in entercloudsuite) @@ -720,16 +740,21 @@ function main() { esac local ip - for dev in eth0 ens3 ; do - ip=$(ip a show dev $dev 2>/dev/null | sed -n "s:.*inet \(.*\)/.*:\1:p") - test "$ip" && break + for dev in $(ip -o link show | awk -F': ' '{print $2}' | grep -v '^lo$'); do + ip=$(ip -4 addr show dev "$dev" | awk '/inet / {print $2}' | cut -d/ -f1) + if [ -n "$ip" ]; then + nameserver="$ip" + break + fi done - : ${nameserver:=$ip} + + local teuthology_branch="$(git -C $(dirname $0)/../../../teuthology rev-parse --abbrev-ref HEAD)" + local teuthology_git_url="$(git -C $(dirname $0)/../../../teuthology config --get remote.origin.url)" if $do_create_config ; then create_config "$network" "$subnets" "$nameserver" "$labdomain" "$ip" \ "$archive_upload" "$canonical_tags" "$selfname" "$keypair" \ - "$server_name" "$server_group" "$worker_group" "$package_repo" || return 1 + "$server_name" "$server_group" "$worker_group" "$package_repo" "$teuthology_branch" "$teuthology_git_url" || return 1 setup_ansible "$subnets" $labdomain || return 1 setup_ssh_config || return 1 setup_authorized_keys || return 1 diff --git a/teuthology/suite/run.py b/teuthology/suite/run.py index ba72a4334c..335df63049 100644 --- a/teuthology/suite/run.py +++ b/teuthology/suite/run.py @@ -7,6 +7,8 @@ import yaml import re import time +from pathlib import Path + from humanfriendly import format_timespan from teuthology import repo_utils @@ -305,7 +307,7 @@ class Run(object): if not teuthology_branch: teuthology_branch = actual_branch teuthology_sha1 = util.git_ls_remote( - f"file://{config.teuthology_path}", + f"file://{Path(config.teuthology_path).resolve()}", teuthology_branch ) else: -- 2.39.5