diff --git a/.dockerignore b/.dockerignore index 9257eaf784..85d46a20d5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -4,10 +4,11 @@ _artifacts bin/ config/ -hack/ docs/ -templates/ -tmp +hack/ +out/ scripts/ +templates/ +tmp/ **/.md tilt-provider.json diff --git a/.zuul.yaml b/.zuul.yaml deleted file mode 100644 index 6536da348b..0000000000 --- a/.zuul.yaml +++ /dev/null @@ -1,53 +0,0 @@ -- project: - name: kubernetes-sigs/cluster-api-provider-openstack - check: - jobs: - - cluster-api-provider-openstack-current-acceptance-test-v1.19-1 - - cluster-api-provider-openstack-current-acceptance-test-v1.19-2 - - cluster-api-provider-openstack-current-acceptance-test-v1.19-3 - -- job: - name: cluster-api-provider-openstack-conformance-pr - parent: init-test - timeout: 36000 - description: | - Test CAPO & Kubernetes - run: .zuul/playbooks/run.yaml - nodeset: ubuntu-bionic-large - vars: - go_version: '1.16' - k8s_log_dir: '{{ ansible_user_dir }}/workspace/logs/_artifacts' - k8s_os_capi_provider_src_dir: '{{ ansible_user_dir }}/src/sigs.k8s.io/cluster-api-provider-openstack' - extra_args: '' - -- job: - name: cluster-api-provider-openstack-current-acceptance-test-v1.19-1 - parent: cluster-api-provider-openstack-conformance-pr - description: | - Test CAPO current against Kubernetes release-1.19-1 - We run 3 jobs, so that at least one of them hits a cluster - with a good CPU. It's fine if just one of them is successful. - vars: - k8s_version_series: '1.19' - extra_args: '--use-ci-artifacts' - -- job: - name: cluster-api-provider-openstack-current-acceptance-test-v1.19-2 - parent: cluster-api-provider-openstack-conformance-pr - description: | - Test CAPO current against Kubernetes release-1.19-2 - We run 3 jobs, so that at least one of them hits a cluster - with a good CPU. It's fine if just one of them is successful. - vars: - k8s_version_series: '1.19' - extra_args: '--use-ci-artifacts' -- job: - name: cluster-api-provider-openstack-current-acceptance-test-v1.19-3 - parent: cluster-api-provider-openstack-conformance-pr - description: | - Test CAPO current against Kubernetes release-1.19-3 - We run 3 jobs, so that at least one of them hits a cluster - with a good CPU. It's fine if just one of them is successful. - vars: - k8s_version_series: '1.19' - extra_args: '--use-ci-artifacts' diff --git a/.zuul/playbooks/run.yaml b/.zuul/playbooks/run.yaml deleted file mode 100644 index d4a389949f..0000000000 --- a/.zuul/playbooks/run.yaml +++ /dev/null @@ -1,103 +0,0 @@ -- hosts: all - become: yes - pre_tasks: - - name: Enable nested virt - shell: - # Enable nested virtualization - # Fail instantly when we got a cluster with: Intel Xeon E3-12xx v2 (Ivy Bridge, IBRS) (won't work anyway..) - # The good ones look like this: Intel Xeon E3-12xx v2 (Ivy Bridge) - cmd: | - # enable nested virt - cat /sys/module/kvm_intel/parameters/nested - sudo rmmod kvm-intel - sudo sh -c "echo 'options kvm-intel nested=y' >> /etc/modprobe.d/dist.conf" - sudo modprobe kvm-intel - cat /sys/module/kvm_intel/parameters/nested - modinfo kvm_intel | grep nested - - if cat /proc/cpuinfo | grep Intel | grep IBRS - then - echo "Detected bad CPU, failing now..." - echo "It's okay if just one of those jobs is successful" - echo "If all fail, it's either an error in the PR or we" - echo "had really bad luck" - exit 1 - else - echo "Detected good CPU" - fi - roles: - - move-k8s-repo-to-k8s-specific-dir - - config-golang - - clone-devstack-gate-to-workspace - - role: create-devstack-local-conf - enable_services: - - 'nova' - - 'neutron' - - 'keystone' - - 'glance' - - role: install-devstack - environment: - DEVSTACK_GATE_LIBVIRT_TYPE: 'kvm' - OS_BRANCH: 'stable/victoria' - OVERRIDE_ENABLED_SERVICES: 'dstat,etcd3,g-api,g-reg,key,mysql,n-api,n-api-meta,n-cond,n-cpu,n-novnc,n-sch,placement-api,q-agt,q-dhcp,q-l3,q-meta,q-metering,q-svc,rabbit' - tasks: - - name: Run kubernetes E2E conformance tests with ClusterAPI OpenStack - shell: - cmd: | - set -xeo pipefail - - source /opt/stack/new/devstack/openrc admin admin - - # Print some infos - nova hypervisor-stats - openstack host list - openstack usage list - openstack project list - openstack network list - openstack subnet list - openstack image list - openstack flavor list - openstack server list - openstack availability zone list - openstack domain list - - openstack flavor delete m1.tiny - openstack flavor create --ram 128 --disk 1 --vcpus 1 --public --id 1 m1.tiny --property hw_rng:allowed='True' - openstack flavor delete m1.small - openstack flavor create --ram 4096 --disk 10 --vcpus 2 --public --id 2 m1.small --property hw_rng:allowed='True' - openstack flavor delete m1.medium - openstack flavor create --ram 6144 --disk 10 --vcpus 4 --public --id 3 m1.medium --property hw_rng:allowed='True' - - # Switch to demo project - source /opt/stack/new/devstack/openrc demo demo - - # Create clouds.yaml - cat << EOF >> /tmp/clouds.yaml - clouds: - capi-quickstart: - auth: - username: ${OS_USERNAME} - password: ${OS_PASSWORD} - user_domain_id: ${OS_USER_DOMAIN_NAME} - auth_url: ${OS_AUTH_URL} - domain_id: default - project_name: demo - verify: false - region_name: RegionOne - EOF - cat /tmp/clouds.yaml - - export ARTIFACTS=/home/zuul/workspace/logs/_artifacts - export OPENSTACK_DNS_NAMESERVERS=8.8.8.8 - export CONTROL_PLANE_MACHINE_COUNT=1 - export WORKER_MACHINE_COUNT=1 - - ./hack/ci/e2e-conformance.sh --install-prereqs --run-tests-parallel --delete-cluster {{ extra_args }} - - # Print some infos - ps aux - df -h - - executable: /bin/bash - chdir: '{{ k8s_os_capi_provider_src_dir }}' - environment: '{{ global_env }}' diff --git a/Makefile b/Makefile index dded503805..7f42deca05 100644 --- a/Makefile +++ b/Makefile @@ -30,6 +30,9 @@ export GOPROXY # Activate module mode, as we use go modules to manage dependencies export GO111MODULE=on +# This option is for running docker manifest command +export DOCKER_CLI_EXPERIMENTAL := enabled + # Directories. TOOLS_DIR := hack/tools TOOLS_BIN_DIR := $(TOOLS_DIR)/bin @@ -201,9 +204,15 @@ generate-manifests: $(CONTROLLER_GEN) ## Generate manifests e.g. CRD, RBAC etc. ## Docker ## -------------------------------------- +.PHONY: docker-pull-prerequisites +docker-pull-prerequisites: + docker pull docker.io/docker/dockerfile:1.1-experimental + docker pull docker.io/library/golang:1.16.0 + docker pull gcr.io/distroless/static:latest + .PHONY: docker-build -docker-build: ## Build the docker image for controller-manager - docker build --pull --build-arg goproxy=$(GOPROXY) --build-arg ARCH=$(ARCH) --build-arg LDFLAGS="$(LDFLAGS)" . -t $(CONTROLLER_IMG)-$(ARCH):$(TAG) +docker-build: docker-pull-prerequisites ## Build the docker image for controller-manager + DOCKER_BUILDKIT=1 docker build --build-arg goproxy=$(GOPROXY) --build-arg ARCH=$(ARCH) --build-arg LDFLAGS="$(LDFLAGS)" . -t $(CONTROLLER_IMG)-$(ARCH):$(TAG) MANIFEST_IMG=$(CONTROLLER_IMG)-$(ARCH) MANIFEST_TAG=$(TAG) $(MAKE) set-manifest-image $(MAKE) set-manifest-pull-policy diff --git a/docs/configuration.md b/docs/configuration.md index f59bcdc4c0..4e34673698 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -40,7 +40,7 @@ Note: You can use [the template file](../templates/cluster-template.yaml) by man # Using 'external-cloud-provider' flavor clusterctl config cluster capi-quickstart \ --flavor external-cloud-provider \ - --kubernetes-version v1.19.7 \ + --kubernetes-version v1.20.4 \ --control-plane-machine-count=3 \ --worker-machine-count=1 \ > capi-quickstart.yaml @@ -48,7 +48,7 @@ clusterctl config cluster capi-quickstart \ # Using 'without-lb' flavor clusterctl config cluster capi-quickstart \ --flavor without-lb \ - --kubernetes-version v1.19.7 \ + --kubernetes-version v1.20.4 \ --control-plane-machine-count=1 \ --worker-machine-count=1 \ > capi-quickstart.yaml diff --git a/hack/boilerplate/boilerplate.py b/hack/boilerplate/boilerplate.py index cabbaa71e8..f64a2a9464 100755 --- a/hack/boilerplate/boilerplate.py +++ b/hack/boilerplate/boilerplate.py @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright 2015 The Kubernetes Authors. # diff --git a/hack/boilerplate/boilerplate.py.txt b/hack/boilerplate/boilerplate.py.txt index a2e72e5988..9fdb989ce7 100644 --- a/hack/boilerplate/boilerplate.py.txt +++ b/hack/boilerplate/boilerplate.py.txt @@ -1,4 +1,4 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # Copyright YEAR The Kubernetes Authors. # diff --git a/hack/boskos.py b/hack/boskos.py new file mode 100644 index 0000000000..bb1bb424aa --- /dev/null +++ b/hack/boskos.py @@ -0,0 +1,126 @@ +#!/usr/bin/env python3 + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import argparse +import json +import os + +import requests +import time + +BOSKOS_HOST = os.environ.get("BOSKOS_HOST", "boskos") +BOSKOS_RESOURCE_NAME = os.environ.get('BOSKOS_RESOURCE_NAME') + + +def checkout_account_request(resource_type, user, input_state): + url = f'http://{BOSKOS_HOST}/acquire?type={resource_type}&state={input_state}&dest=busy&owner={user}' + r = requests.post(url) + status = r.status_code + reason = r.reason + result = "" + + if status == 200: + content = r.content.decode() + result = json.loads(content) + + return status, reason, result + + +def checkout_account(resource_type, user): + status, reason, result = checkout_account_request(resource_type, user, "clean") + # TODO(sbueringer): find out if we still need this + # replicated the implementation of cluster-api-provider-gcp + # we're working around an issue with the data in boskos. + # We'll remove the code that tries both free and clean once all the data is good. + # Afterwards we should just check for free + if status == 404: + status, reason, result = checkout_account_request(resource_type, user, "free") + + if status != 200: + raise Exception(f"Got invalid response {status}: {reason}") + + print(f"export BOSKOS_RESOURCE_NAME={result['name']}") + print(f"export GCP_PROJECT={result['name']}") + + +def release_account(user): + url = f'http://{BOSKOS_HOST}/release?name={BOSKOS_RESOURCE_NAME}&dest=dirty&owner={user}' + + r = requests.post(url) + + if r.status_code != 200: + raise Exception(f"Got invalid response {r.status_code}: {r.reason}") + + +def send_heartbeat(user): + url = f'http://{BOSKOS_HOST}/update?name={BOSKOS_RESOURCE_NAME}&state=busy&owner={user}' + + while True: + print(f"POST-ing heartbeat for resource {BOSKOS_RESOURCE_NAME} to {BOSKOS_HOST}") + r = requests.post(url) + + if r.status_code == 200: + print(f"response status: {r.status_code}") + else: + print(f"Got invalid response {r.status_code}: {r.reason}") + + time.sleep(60) + + +def main(): + parser = argparse.ArgumentParser(description='Boskos GCP Account Management') + + parser.add_argument( + '--get', dest='checkout_account', action="store_true", + help='Checkout a Boskos GCP Account' + ) + + parser.add_argument( + '--release', dest='release_account', action="store_true", + help='Release a Boskos GCP Account' + ) + + parser.add_argument( + '--heartbeat', dest='send_heartbeat', action="store_true", + help='Send heartbeat for the checked out a Boskos GCP Account' + ) + + parser.add_argument( + '--resource-type', dest="resource_type", type=str, + default="gce-project", + help="Type of Boskos resource to manage" + ) + + parser.add_argument( + '--user', dest="user", type=str, + default="cluster-api-provider-openstack", + help="username" + ) + + args = parser.parse_args() + + if args.checkout_account: + checkout_account(args.resource_type, args.user) + + elif args.release_account: + release_account(args.user) + + elif args.send_heartbeat: + send_heartbeat(args.user) + + +if __name__ == "__main__": + main() diff --git a/hack/ci/.gitignore b/hack/ci/.gitignore new file mode 100644 index 0000000000..40f662fb90 --- /dev/null +++ b/hack/ci/.gitignore @@ -0,0 +1 @@ +e2e-conformance-gcp-cloud-init.yaml \ No newline at end of file diff --git a/hack/ci/e2e-conformance-gcp-cloud-init.yaml.tpl b/hack/ci/e2e-conformance-gcp-cloud-init.yaml.tpl new file mode 100644 index 0000000000..efe09995fd --- /dev/null +++ b/hack/ci/e2e-conformance-gcp-cloud-init.yaml.tpl @@ -0,0 +1,103 @@ +#cloud-config +hostname: openstack +password: ubuntu +chpasswd: { expire: False } +ssh_pwauth: True +write_files: + - content: | + net.ipv4.ip_forward=1 + net.ipv4.conf.default.rp_filter=0 + net.ipv4.conf.all.rp_filter=0 + path: /etc/sysctl.d/devstack.conf + - content: | + #!/bin/bash + + set -o errexit -o nounset -o pipefail + + # Install kvm / ensure nested virtualization + sudo apt-get update && sudo apt-get install qemu-kvm jq net-tools -y + kvm-ok + sudo modprobe kvm-intel + + # from https://raw.githubusercontent.com/openstack/octavia/master/devstack/contrib/new-octavia-devstack.sh + git clone -b stable/${OPENSTACK_RELEASE} https://github.com/openstack/devstack.git /tmp/devstack + + cat < /tmp/devstack/localrc + GIT_BASE=https://github.com + + # Neutron + enable_plugin neutron https://github.com/openstack/neutron stable/${OPENSTACK_RELEASE} + + # Octavia + enable_plugin octavia https://github.com/openstack/octavia stable/${OPENSTACK_RELEASE} + enable_plugin octavia-dashboard https://github.com/openstack/octavia-dashboard stable/${OPENSTACK_RELEASE} + #LIBS_FROM_GIT+=python-octaviaclient + + # Cinder + enable_plugin cinderlib https://github.com/openstack/cinderlib stable/${OPENSTACK_RELEASE} + + KEYSTONE_TOKEN_FORMAT=fernet + + SERVICE_TIMEOUT=240 + + DATABASE_PASSWORD=secretdatabase + RABBIT_PASSWORD=secretrabbit + ADMIN_PASSWORD=secretadmin + SERVICE_PASSWORD=secretservice + SERVICE_TOKEN=111222333444 + # Enable Logging + LOGFILE=/opt/stack/logs/stack.sh.log + VERBOSE=True + LOG_COLOR=True + + # Pre-requisite + ENABLED_SERVICES=key,rabbit,mysql + # Nova + ENABLED_SERVICES+=,n-api,n-obj,n-cpu,n-cond,n-sch,n-novnc,n-api-meta + # Placement service needed for Nova + ENABLED_SERVICES+=,placement-api,placement-client + # Glance + ENABLED_SERVICES+=,g-api,g-reg + + # Octavia-Neutron + ENABLED_SERVICES+=,neutron-api,neutron-agent,neutron-dhcp,neutron-l3 + ENABLED_SERVICES+=,neutron-metadata-agent,neutron-qos + # Octavia + ENABLED_SERVICES+=,octavia,o-api,o-cw,o-hm,o-hk,o-da + + # Horizon (enable for manual tests) + #ENABLED_SERVICES+=,horizon + + # Cinder + ENABLED_SERVICES+=,c-sch,c-api,c-vol + + LIBVIRT_TYPE=kvm + + # Don't download default images, just our test images + DOWNLOAD_DEFAULT_IMAGES=False + IMAGE_URLS="https://github.com/kubernetes-sigs/cluster-api-provider-openstack/releases/download/v0.3.0/ubuntu-1910-kube-v1.17.3.qcow2," + IMAGE_URLS+="http://download.cirros-cloud.net/0.5.1/cirros-0.5.1-x86_64-disk.img" + EOF + + # Create the stack user + /tmp/devstack/tools/create-stack-user.sh + + # Move everything into place (/opt/stack is the $HOME folder of the stack user) + mv /tmp/devstack /opt/stack/ + chown -R stack:stack /opt/stack/devstack/ + + # Stack that stack! + su - stack -c /opt/stack/devstack/stack.sh + + # Add environment variables for auth/endpoints + echo 'source /opt/stack/devstack/openrc admin admin' >> /opt/stack/.bashrc + + sudo iptables -t nat -I POSTROUTING -o ens4 -s 172.24.4.0/24 -j MASQUERADE + sudo iptables -I FORWARD -s 172.24.4.0/24 -j ACCEPT + + path: /root/devstack.sh + permissions: '0777' +runcmd: + - sysctl -p /etc/sysctl.d/devstack.conf + - /root/devstack.sh +final_message: "The system is finally up, after $UPTIME seconds" diff --git a/hack/ci/e2e-conformance-gcp-prepare.sh b/hack/ci/e2e-conformance-gcp-prepare.sh new file mode 100755 index 0000000000..2fb9a4920b --- /dev/null +++ b/hack/ci/e2e-conformance-gcp-prepare.sh @@ -0,0 +1,215 @@ +#!/usr/bin/env bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# hack script for preparing gcp to run cluster-api-provider-openstack e2e + +set -o errexit -o nounset -o pipefail + +CLUSTER_NAME=${CLUSTER_NAME:-"capi-quickstart"} +GOOGLE_APPLICATION_CREDENTIALS=${GOOGLE_APPLICATION_CREDENTIALS:-""} +GCP_PROJECT=${GCP_PROJECT:-""} +GCP_REGION=${GCP_REGION:-"us-east4"} +GCP_ZONE=${GCP_ZONE:-"us-east4-a"} +GCP_MACHINE_MIN_CPU_PLATFORM=${GCP_MACHINE_MIN_CPU_PLATFORM:-"Intel Cascade Lake"} +GCP_MACHINE_TYPE=${GCP_MACHINE_TYPE:-"n2-standard-16"} +GCP_NETWORK_NAME=${GCP_NETWORK_NAME:-"${CLUSTER_NAME}-mynetwork"} +OPENSTACK_RELEASE=${OPENSTACK_RELEASE:-"victoria"} + +echo "Using: GCP_PROJECT: ${GCP_PROJECT} GCP_REGION: ${GCP_REGION} GCP_NETWORK_NAME: ${GCP_NETWORK_NAME}" + +# retry $1 times with $2 sleep in between +function retry { + attempt=0 + max_attempts=${1} + interval=${2} + shift; shift + until [[ ${attempt} -ge "${max_attempts}" ]] ; do + attempt=$((attempt+1)) + set +e + eval "$*" && return || echo "failed ${attempt} times: $*" + set -e + sleep "${interval}" + done + echo "error: reached max attempts at retry($*)" + return 1 +} + +function init_networks() { + if [[ ${GCP_NETWORK_NAME} != "default" ]]; then + if ! gcloud compute networks describe "${GCP_NETWORK_NAME}" --project "${GCP_PROJECT}" > /dev/null; + then + gcloud compute networks create --project "$GCP_PROJECT" "${GCP_NETWORK_NAME}" --subnet-mode auto --quiet + gcloud compute firewall-rules create "${GCP_NETWORK_NAME}"-allow-http --project "$GCP_PROJECT" \ + --allow tcp:80 --network "${GCP_NETWORK_NAME}" --quiet + gcloud compute firewall-rules create "${GCP_NETWORK_NAME}"-allow-https --project "$GCP_PROJECT" \ + --allow tcp:443 --network "${GCP_NETWORK_NAME}" --quiet + gcloud compute firewall-rules create "${GCP_NETWORK_NAME}"-allow-icmp --project "$GCP_PROJECT" \ + --allow icmp --network "${GCP_NETWORK_NAME}" --priority 65534 --quiet + gcloud compute firewall-rules create "${GCP_NETWORK_NAME}"-allow-internal --project "$GCP_PROJECT" \ + --allow "tcp:0-65535,udp:0-65535,icmp" --network "${GCP_NETWORK_NAME}" --priority 65534 --quiet + gcloud compute firewall-rules create "${GCP_NETWORK_NAME}"-allow-rdp --project "$GCP_PROJECT" \ + --allow "tcp:3389" --network "${GCP_NETWORK_NAME}" --priority 65534 --quiet + gcloud compute firewall-rules create "${GCP_NETWORK_NAME}"-allow-ssh --project "$GCP_PROJECT" \ + --allow "tcp:22" --network "${GCP_NETWORK_NAME}" --priority 65534 --quiet + fi + fi + + gcloud compute firewall-rules list --project "$GCP_PROJECT" + gcloud compute networks list --project="${GCP_PROJECT}" + gcloud compute networks describe "${GCP_NETWORK_NAME}" --project="${GCP_PROJECT}" + + if ! gcloud compute routers describe "${CLUSTER_NAME}-myrouter" --project="${GCP_PROJECT}" --region="${GCP_REGION}" > /dev/null; + then + gcloud compute routers create "${CLUSTER_NAME}-myrouter" --project="${GCP_PROJECT}" \ + --region="${GCP_REGION}" --network="${GCP_NETWORK_NAME}" + fi + if ! gcloud compute routers nats describe --router="${CLUSTER_NAME}-myrouter" "${CLUSTER_NAME}-mynat" --project="${GCP_PROJECT}" --region="${GCP_REGION}" > /dev/null; + then + gcloud compute routers nats create "${CLUSTER_NAME}-mynat" --project="${GCP_PROJECT}" \ + --router-region="${GCP_REGION}" --router="${CLUSTER_NAME}-myrouter" \ + --nat-all-subnet-ip-ranges --auto-allocate-nat-external-ips + fi +} + +main() { + # Initialize the necessary network requirements + if [[ -n "${SKIP_INIT_NETWORK:-}" ]]; then + echo "Skipping network initialization..." + else + init_networks + fi + + if ! gcloud compute disks describe ubuntu2004disk --project "${GCP_PROJECT}" --zone "${GCP_ZONE}" > /dev/null; + then + gcloud compute disks create ubuntu2004disk \ + --project "${GCP_PROJECT}" \ + --image-project ubuntu-os-cloud --image-family ubuntu-2004-lts \ + --zone "${GCP_ZONE}" + fi + + if ! gcloud compute images describe ubuntu-2004-nested --project "${GCP_PROJECT}" > /dev/null; + then + gcloud compute images create ubuntu-2004-nested \ + --project "${GCP_PROJECT}" \ + --source-disk ubuntu2004disk --source-disk-zone "${GCP_ZONE}" \ + --licenses "https://www.googleapis.com/compute/v1/projects/vm-options/global/licenses/enable-vmx" + fi + + if ! gcloud compute instances describe openstack --project "${GCP_PROJECT}" --zone "${GCP_ZONE}" > /dev/null; + then + < ./hack/ci/e2e-conformance-gcp-cloud-init.yaml.tpl \ + sed "s|\${OPENSTACK_RELEASE}|${OPENSTACK_RELEASE}|" \ + > ./hack/ci/e2e-conformance-gcp-cloud-init.yaml + + gcloud compute instances create openstack \ + --project "${GCP_PROJECT}" \ + --zone "${GCP_ZONE}" \ + --image ubuntu-2004-nested \ + --boot-disk-size 100G \ + --boot-disk-type pd-ssd \ + --can-ip-forward \ + --tags http-server,https-server,novnc,openstack-apis \ + --min-cpu-platform "${GCP_MACHINE_MIN_CPU_PLATFORM}" \ + --machine-type "${GCP_MACHINE_TYPE}" \ + --network-interface=network="${CLUSTER_NAME}-mynetwork,subnet=${CLUSTER_NAME}-mynetwork,aliases=/24" \ + --metadata-from-file user-data=./hack/ci/e2e-conformance-gcp-cloud-init.yaml + fi + + # Install some local deps we later need in the meantime (we have to wait for cloud init anyway) + if ! command -v sshuttle; + then + # Install sshuttle from source because we need: https://github.com/sshuttle/sshuttle/pull/606 + # TODO(sbueringer) install via pip after the next release after 1.0.5 via: + # pip3 install sshuttle + cd /tmp + git clone https://github.com/sshuttle/sshuttle.git + cd sshuttle + pip3 install . + fi + if ! command -v openstack; + then + apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y python3-dev + # install PyYAML first because otherwise we get an error because pip3 doesn't upgrade PyYAML to the correct version + # ERROR: Cannot uninstall 'PyYAML'. It is a distutils installed project and thus we cannot accurately determine which + # files belong to it which would lead to only a partial uninstall. + pip3 install --ignore-installed PyYAML + pip3 install python-cinderclient python-glanceclient python-keystoneclient python-neutronclient python-novaclient python-openstackclient python-octaviaclient + fi + + # Wait until cloud-init is done + retry 120 30 "gcloud compute ssh --project ${GCP_PROJECT} --zone ${GCP_ZONE} openstack -- cat /var/lib/cloud/instance/boot-finished" + + # Open tunnel + PUBLIC_IP=$(gcloud compute instances describe openstack --project "${GCP_PROJECT}" --zone "${GCP_ZONE}" --format='get(networkInterfaces[0].accessConfigs[0].natIP)') + PRIVATE_IP=$(gcloud compute instances describe openstack --project "${GCP_PROJECT}" --zone "${GCP_ZONE}" --format='get(networkInterfaces[0].networkIP)') + echo "Opening tunnel to ${PRIVATE_IP} via ${PUBLIC_IP}" + # Packets from the Prow Pod or the Pods in Kind have TTL 63 or 64. + # We need a ttl of 65 (default 63), so all of our packets are captured by sshuttle. + sshuttle -r "${PUBLIC_IP}" "${PRIVATE_IP}/32" 172.24.4.0/24 --ttl=65 --ssh-cmd='ssh -i ~/.ssh/google_compute_engine -o "StrictHostKeyChecking no" -o "UserKnownHostsFile=/dev/null" -o "IdentitiesOnly=yes"' -l 0.0.0.0 -D + + export OS_REGION_NAME=RegionOne + export OS_PROJECT_DOMAIN_ID=default + export OS_AUTH_URL=http://${PRIVATE_IP}/identity + export OS_TENANT_NAME=admin + export OS_USER_DOMAIN_ID=default + export OS_USERNAME=admin + export OS_PROJECT_NAME=admin + export OS_PASSWORD=secretadmin + export OS_IDENTITY_API_VERSION=3 + + # Wait until the OpenStack API is reachable + retry 120 30 "openstack versions show" + + nova hypervisor-stats + openstack host list + openstack usage list + openstack project list + openstack network list + openstack subnet list + openstack image list + openstack flavor list + openstack server list + openstack availability zone list + openstack domain list + + openstack flavor delete m1.tiny + openstack flavor create --ram 512 --disk 1 --vcpus 1 --public --id 1 m1.tiny --property hw_rng:allowed='True' + openstack flavor delete m1.small + openstack flavor create --ram 6144 --disk 10 --vcpus 2 --public --id 2 m1.small --property hw_rng:allowed='True' + openstack flavor delete m1.medium + openstack flavor create --ram 6144 --disk 10 --vcpus 4 --public --id 3 m1.medium --property hw_rng:allowed='True' + + export OS_TENANT_NAME=demo + export OS_USERNAME=demo + export OS_PROJECT_NAME=demo + + cat << EOF > /tmp/clouds.yaml +clouds: + ${CLUSTER_NAME}: + auth: + username: ${OS_USERNAME} + password: ${OS_PASSWORD} + user_domain_id: ${OS_USER_DOMAIN_ID} + auth_url: ${OS_AUTH_URL} + domain_id: default + project_name: demo + verify: false + region_name: RegionOne +EOF + cat /tmp/clouds.yaml +} + +main "$@" diff --git a/hack/ci/e2e-conformance.sh b/hack/ci/e2e-conformance.sh index 59ac75b34f..b8172a224a 100755 --- a/hack/ci/e2e-conformance.sh +++ b/hack/ci/e2e-conformance.sh @@ -23,251 +23,271 @@ OPENSTACK_CLOUD_YAML_FILE=${OPENSTACK_CLOUD_YAML_FILE:-"/tmp/clouds.yaml"} IMAGE_URL="https://github.com/kubernetes-sigs/cluster-api-provider-openstack/releases/download/v0.3.0/ubuntu-1910-kube-v1.17.3.qcow2" CIRROS_URL="http://download.cirros-cloud.net/0.5.1/cirros-0.5.1-x86_64-disk.img" OPENSTACK_IMAGE_NAME=${OPENSTACK_IMAGE_NAME:-"ubuntu-1910-kube-v1.17.3"} -OPENSTACK_BASTION_IMAGE_NAME=${OPENSTACK_BASTION_IMAGE_NAME:-"cirros"} +OPENSTACK_BASTION_IMAGE_NAME=${OPENSTACK_BASTION_IMAGE_NAME:-"cirros-0.5.1-x86_64-disk"} OPENSTACK_FAILURE_DOMAIN=${OPENSTACK_FAILURE_DOMAIN:-"nova"} OPENSTACK_DNS_NAMESERVERS=${OPENSTACK_DNS_NAMESERVERS:-"192.168.200.1"} OPENSTACK_NODE_MACHINE_FLAVOR=${OPENSTACK_NODE_MACHINE_FLAVOR:-"m1.small"} -WORKER_MACHINE_COUNT=${WORKER_MACHINE_COUNT:-"3"} +WORKER_MACHINE_COUNT=${WORKER_MACHINE_COUNT:-"4"} OPENSTACK_CONTROL_PLANE_MACHINE_FLAVOR=${OPENSTACK_CONTROL_PLANE_MACHINE_FLAVOR:-"m1.medium"} CONTROL_PLANE_MACHINE_COUNT=${CONTROL_PLANE_MACHINE_COUNT:-"1"} OPENSTACK_BASTION_MACHINE_FLAVOR=${OPENSTACK_BASTION_MACHINE_FLAVOR:-"m1.tiny"} -OPENSTACK_CLUSTER_TEMPLATE=${OPENSTACK_CLUSTER_TEMPLATE:-"./templates/cluster-template-without-lb.yaml"} +OPENSTACK_CLUSTER_TEMPLATE=${OPENSTACK_CLUSTER_TEMPLATE:-"./templates/cluster-template.yaml"} CLUSTER_NAME=${CLUSTER_NAME:-"capi-quickstart"} OPENSTACK_SSH_KEY_NAME=${OPENSTACK_SSH_KEY_NAME:-"${CLUSTER_NAME}-key"} -KUBERNETES_VERSION=${KUBERNETES_VERSION:-"v1.19.7"} +KUBERNETES_VERSION=${KUBERNETES_VERSION:-"v1.20.4"} USE_CI_ARTIFACTS=${USE_CI_ARTIFACTS:-"true"} IMAGE_REPOSITORY=${IMAGE_REPOSITORY:-"k8s.gcr.io"} ARTIFACTS="${ARTIFACTS:-${PWD}/_artifacts}" REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd -P)" -LOGS_KIND_DUMPED=false +LOGS_DEVSTACK_DUMPED=false +LOGS_MGMT_DUMPED=false LOGS_CAPO_DUMPED=false +dump_devstack_logs() { + set -x + + if [[ "${LOGS_DEVSTACK_DUMPED}" == "true" ]]; + then + echo "mgmt logs already dumped" + return 0 + fi + LOGS_DEVSTACK_DUMPED=true + + echo "Dump logs" + dir="${ARTIFACTS}/logs/devstack" + mkdir -p "${dir}" + + # e.g.: http://10.150.0.2/identity => 10.150.0.2 + DEVSTACK_IP=$(echo "$CAPO_AUTH_URL" | awk -F[/:] '{print $4}') + scp -i ~/.ssh/google_compute_engine \ + -o "StrictHostKeyChecking no" -o "UserKnownHostsFile=/dev/null" -o "IdentitiesOnly=yes" \ + -r "root@${DEVSTACK_IP}:/var/log/cloud-init.log" "root@${DEVSTACK_IP}:/var/log/cloud-init-output.log" \ + "${dir}" || true +} + dump_kind_logs() { set -x - if [[ "${LOGS_KIND_DUMPED}" == "true" ]]; + if [[ "${LOGS_MGMT_DUMPED}" == "true" ]]; then - echo "kind logs already dumped" + echo "mgmt logs already dumped" return 0 fi - LOGS_KIND_DUMPED=true + LOGS_MGMT_DUMPED=true + + iptables -t nat -L --line-numbers || true echo "Dump logs" - mkdir -p "${ARTIFACTS}/logs" + mkdir -p "${ARTIFACTS}/logs/mgmt" echo "=== versions ===" echo "kind : $(kind version)" || true - echo "bootstrap cluster:" + echo "mgmt cluster:" kubectl version || true echo "" # dump all the info from the CAPI related CRDs - kubectl get clusters,openstackclusters,machines,openstackmachines,kubeadmconfigs,machinedeployments,openstackmachinetemplates,kubeadmconfigtemplates,machinesets --all-namespaces -o yaml > "${ARTIFACTS}/logs/kind-capo.txt" || true - - # dump cluster info for kind - kubectl cluster-info dump > "${ARTIFACTS}/logs/kind-cluster.txt" || true - kubectl get secrets -o yaml -A > "${ARTIFACTS}/logs/kind-cluster-secrets.txt" || true + kubectl get clusters,openstackclusters,machines,openstackmachines,kubeadmconfigs,machinedeployments,openstackmachinetemplates,kubeadmconfigtemplates,machinesets --all-namespaces -o yaml > "${ARTIFACTS}/logs/mgmt/capo.txt" || true # dump images info - echo "images in docker" >> "${ARTIFACTS}/logs/images.txt" - docker images >> "${ARTIFACTS}/logs/images.txt" - echo "images from bootstrap using containerd CLI" >> "${ARTIFACTS}/logs/images.txt" - docker exec clusterapi-control-plane ctr -n k8s.io images list >> "${ARTIFACTS}/logs/images.txt" || true - echo "images in bootstrap cluster using kubectl CLI" >> "${ARTIFACTS}/logs/images.txt" + { echo "=== images in docker using docker images ==="; docker images;} >> "${ARTIFACTS}/logs/mgmt/images.txt" + echo "=== images in mgmt cluster using containerd CLI ===" >> "${ARTIFACTS}/logs/mgmt/images.txt" + docker exec clusterapi-control-plane ctr -n k8s.io images list >> "${ARTIFACTS}/logs/mgmt/images.txt" || true + echo "=== images in mgmt cluster using kubectl CLI" >> "${ARTIFACTS}/logs/mgmt/images.txt" (kubectl get pods --all-namespaces -o json \ - | jq --raw-output '.items[].spec.containers[].image' | sort) >> "${ARTIFACTS}/logs/images.txt" || true - sudo journalctl --output=short-precise -k -b all > "${ARTIFACTS}/logs/kern.txt" || true - sudo journalctl --output=short-precise > "${ARTIFACTS}/logs/systemd.txt" || true - sudo last -x > "${ARTIFACTS}/logs/last-x.txt" || true + | jq --raw-output '.items[].spec.containers[].image' | sort) >> "${ARTIFACTS}/logs/mgmt/images.txt" || true + + # dump cluster info for mgmt + kubectl cluster-info dump > "${ARTIFACTS}/logs/mgmt/cluster.txt" || true + kubectl get secrets -o yaml -A > "${ARTIFACTS}/logs/mgmt/cluster-secrets.txt" || true # export all logs from kind - kind "export" logs --name="clusterapi" "${ARTIFACTS}/logs" || true + kind export logs --name="clusterapi" "${ARTIFACTS}/logs/mgmt" || true set +x } -dump_capo_logs() { +dump_workload_logs() { set -x if [[ "${LOGS_CAPO_DUMPED}" == "true" ]]; then - echo "capo logs already dumped" + echo "workload logs already dumped" return 0 fi LOGS_CAPO_DUMPED=true echo "Dump logs" - mkdir -p "${ARTIFACTS}/logs" + mkdir -p "${ARTIFACTS}/logs/workload" echo "=== versions ===" - echo "capo cluster:" - kubectl --kubeconfig=${PWD}/kubeconfig version || true + echo "workload cluster:" + kubectl --kubeconfig="${PWD}/kubeconfig" version || true echo "" # dump images info - echo "images in deployed cluster using kubectl CLI" >> "${ARTIFACTS}/logs/images.txt" + echo "=== images in workload cluster using kubectl CLI ===" >> "${ARTIFACTS}/logs/workload/images.txt" (kubectl --kubeconfig="${PWD}"/kubeconfig get pods --all-namespaces -o json \ - | jq --raw-output '.items[].spec.containers[].image' | sort) >> "${ARTIFACTS}/logs/images.txt" || true + | jq --raw-output '.items[].spec.containers[].image' | sort) >> "${ARTIFACTS}/logs/workload/images.txt" || true + + # dump cluster info for workload + kubectl --kubeconfig="${PWD}/kubeconfig" cluster-info dump >> "${ARTIFACTS}/logs/workload/cluster.txt" || true + kubectl --kubeconfig="${PWD}/kubeconfig" get secrets -o yaml -A > "${ARTIFACTS}/logs/workload/cluster-secrets.txt" || true # dump OpenStack info - echo "" > "${ARTIFACTS}/logs/openstack-cluster.txt" - echo "=== OpenStack compute images list ===" >> "${ARTIFACTS}/logs/openstack-cluster.txt" || true - openstack image list >> "${ARTIFACTS}/logs/openstack-cluster.txt" || true - echo "=== OpenStack compute instances list ===" >> "${ARTIFACTS}/logs/openstack-cluster.txt" || true - openstack server list >> "${ARTIFACTS}/logs/openstack-cluster.txt" || true - echo "=== OpenStack compute instances show ===" >> "${ARTIFACTS}/logs/openstack-cluster.txt" || true - openstack server list -f value -c Name | xargs -I% openstack server show % >> "${ARTIFACTS}/logs/openstack-cluster.txt" || true - echo "=== cluster-info dump ===" >> "${ARTIFACTS}/logs/openstack-cluster.txt" || true - kubectl --kubeconfig=${PWD}/kubeconfig cluster-info dump >> "${ARTIFACTS}/logs/openstack-cluster.txt" || true - kubectl --kubeconfig=${PWD}/kubeconfig get secrets -o yaml -A > "${ARTIFACTS}/logs/openstack-cluster-secrets.txt" || true - - jump_node_name=$(openstack server list -f value -c Name | grep ${CLUSTER_NAME}-bastion | head -n 1) - jump_node=$(openstack server show ${jump_node_name} -f value -c addresses | awk '{print $2}') + echo "=== OpenStack compute images list ===" >> "${ARTIFACTS}/logs/workload/openstack.txt" || true + openstack image list >> "${ARTIFACTS}/logs/workload/openstack.txt" || true + echo "=== OpenStack compute instances list ===" >> "${ARTIFACTS}/logs/workload/openstack.txt" || true + openstack server list >> "${ARTIFACTS}/logs/workload/openstack.txt" || true + echo "=== OpenStack compute instances show ===" >> "${ARTIFACTS}/logs/workload/openstack.txt" || true + openstack server list -f value -c Name | xargs -I% openstack server show % >> "${ARTIFACTS}/logs/workload/openstack.txt" || true + + jump_node_name=$(openstack server list -f value -c Name | grep "${CLUSTER_NAME}-bastion" | head -n 1) + jump_node=$(openstack server show "${jump_node_name}" -f value -c addresses | awk '{print $2}') + ssh_config="${ARTIFACTS}/ssh_config" + cat > "${ssh_config}" < "${dir}/console.log" || true + openstack console log show "${node}" > "${dir}/openstack-console.log" || true + node=$(openstack port show "${node}" -f json -c fixed_ips | jq '.fixed_ips[0].ip_address' -r) - ssh_key_pem="/tmp/${OPENSTACK_SSH_KEY_NAME}.pem" - PROXY_COMMAND="ssh -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30 -x -W %h:22 -i ${ssh_key_pem} cirros@${jump_node}" - node=$(openstack port show ${node} -f json -c fixed_ips | jq '.fixed_ips[0].ip_address' -r) - - ssh-to-node "${node}" "${jump_node}" "sudo chmod -R a+r /var/log" || true - scp -r -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -o ConnectTimeout=30 -o ProxyCommand="${PROXY_COMMAND}" -i ${ssh_key_pem} \ - "ubuntu@${node}:/var/log/cloud-init.log" "ubuntu@${node}:/var/log/cloud-init-output.log" \ - "ubuntu@${node}:/var/log/pods" "ubuntu@${node}:/var/log/containers" \ + ssh -F "${ssh_config}" "capi@${node}" "sudo chmod -R a+r /var/log" || true + scp -F "${ssh_config}" -r \ + "capi@${node}:/var/log/cloud-init.log" "capi@${node}:/var/log/cloud-init-output.log" \ + "capi@${node}:/var/log/pods" "capi@${node}:/var/log/containers" \ "${dir}" || true - ssh-to-node "${node}" "${jump_node}" "sudo journalctl --output=short-precise -k" > "${dir}/kern.log" || true - ssh-to-node "${node}" "${jump_node}" "sudo journalctl --output=short-precise" > "${dir}/systemd.log" || true - ssh-to-node "${node}" "${jump_node}" "sudo crictl version && sudo crictl info" > "${dir}/containerd.txt" || true - ssh-to-node "${node}" "${jump_node}" "sudo journalctl --no-pager -u cloud-final" > "${dir}/cloud-final.log" || true - ssh-to-node "${node}" "${jump_node}" "sudo journalctl --no-pager -u kubelet.service" > "${dir}/kubelet.log" || true - ssh-to-node "${node}" "${jump_node}" "sudo journalctl --no-pager -u containerd.service" > "${dir}/containerd.log" || true - ssh-to-node "${node}" "${jump_node}" "sudo top -b -n 1" > "${dir}/top.txt" || true - ssh-to-node "${node}" "${jump_node}" "sudo crictl ps" > "${dir}/crictl-ps.log" || true - ssh-to-node "${node}" "${jump_node}" "sudo crictl pods" > "${dir}/crictl-pods.log" || true + ssh -F "${ssh_config}" "capi@${node}" "sudo journalctl --output=short-precise -k -b all" > "${dir}/kernel.log" || true + ssh -F "${ssh_config}" "capi@${node}" "sudo journalctl --output=short-precise" > "${dir}/systemd.log" || true + ssh -F "${ssh_config}" "capi@${node}" "sudo crictl version && sudo crictl info" > "${dir}/containerd.txt" || true + ssh -F "${ssh_config}" "capi@${node}" "sudo journalctl --no-pager -u cloud-final" > "${dir}/cloud-final.log" || true + ssh -F "${ssh_config}" "capi@${node}" "sudo journalctl --no-pager -u kubelet.service" > "${dir}/kubelet.log" || true + ssh -F "${ssh_config}" "capi@${node}" "sudo journalctl --no-pager -u containerd.service" > "${dir}/containerd.log" || true + ssh -F "${ssh_config}" "capi@${node}" "sudo top -b -n 1" > "${dir}/top.txt" || true + ssh -F "${ssh_config}" "capi@${node}" "sudo crictl ps" > "${dir}/crictl-ps.log" || true + ssh -F "${ssh_config}" "capi@${node}" "sudo crictl pods" > "${dir}/crictl-pods.log" || true done set +x } function dump_logs() { + dump_devstack_logs dump_kind_logs - dump_capo_logs -} - -# SSH to a node by name ($1) via jump server ($2) and run a command ($3). -function ssh-to-node() { - local node="$1" - local jump="$2" - local cmd="$3" - - ssh_key_pem="/tmp/${OPENSTACK_SSH_KEY_NAME}.pem" - ssh_params="-o LogLevel=quiet -o ConnectTimeout=30 -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no" - scp $ssh_params -i $ssh_key_pem $ssh_key_pem "cirros@${jump}:$ssh_key_pem" - ssh $ssh_params -i $ssh_key_pem \ - -o "ProxyCommand ssh $ssh_params -W %h:%p -i $ssh_key_pem cirros@${jump}" \ - ubuntu@"${node}" "${cmd}" + dump_workload_logs } create_key_pair() { - echo "Create key pair" + echo "Create and upload key pair" if [[ -f /tmp/${OPENSTACK_SSH_KEY_NAME}.pem ]] && [[ -f /tmp/${OPENSTACK_SSH_KEY_NAME}.pem.pub ]] then - echo "Skipping, key pair already exists" - return 0 + echo "Skip generating key pair, it already exists" + else + ssh-keygen -t rsa -f "/tmp/${OPENSTACK_SSH_KEY_NAME}.pem" -N "" + chmod 0400 "/tmp/${OPENSTACK_SSH_KEY_NAME}.pem" fi + OPENSTACK_SSH_KEY_PUBLIC=$(cat "/tmp/${OPENSTACK_SSH_KEY_NAME}.pem.pub") - ssh-keygen -t rsa -f "/tmp/${OPENSTACK_SSH_KEY_NAME}.pem" -N "" - chmod 0400 "/tmp/${OPENSTACK_SSH_KEY_NAME}.pem" - - openstack keypair create --public-key "/tmp/${OPENSTACK_SSH_KEY_NAME}.pem.pub" ${OPENSTACK_SSH_KEY_NAME} + if ! openstack keypair show "${OPENSTACK_SSH_KEY_NAME}"; + then + openstack keypair create --public-key "/tmp/${OPENSTACK_SSH_KEY_NAME}.pem.pub" "${OPENSTACK_SSH_KEY_NAME}" + fi } -upload_image() { - # $1: image name - # $2: image url +upload_image(){ + IMAGE_NAME=$1 + IMAGE_URL=$2 echo "Upload image" # Remove old image if we don't want to reuse it if [[ "${REUSE_OLD_IMAGES:-true}" == "false" ]]; then - image_id=$(openstack image list --name=$1 -f value -c ID) - if [[ ! -z "$image_id" ]]; then - echo "Deleting old image $1 with id: ${image_id}" - openstack image delete ${image_id} + image_id=$(openstack image list --name="${IMAGE_NAME}" -f value -c ID) + if [[ -n "$image_id" ]]; then + echo "Deleting old image ${IMAGE_NAME} with id: ${image_id}" + openstack image delete "${image_id}" fi fi - image=$(openstack image list --name=$1 -f value -c Name) - if [[ ! -z "$image" ]]; then - echo "Image $1 already exists" + image=$(openstack image list --name="${IMAGE_NAME}" -f value -c Name) + if [[ -n "$image" ]]; then + echo "Image ""${IMAGE_NAME}"" already exists" return fi - tmpfile=$(mktemp) - echo "Download image $1 from $2" - wget -q -c $2 -O ${tmpfile} + tmpfile=/tmp/"${IMAGE_NAME}" + if [ ! -f "${tmpfile}" ]; + then + echo "Download image ${IMAGE_NAME} from IMAGE_URL" + wget -q -c "IMAGE_URL" -O "${tmpfile}" + fi - echo "Uploading image $1" - openstack image create --disk-format qcow2 --private --container-format bare --file "${tmpfile}" $1 + echo "Uploading image ${IMAGE_NAME}" + openstack image create --disk-format qcow2 --private --container-format bare --file "${tmpfile}" "${IMAGE_NAME}" } install_prereqs() { - # Install Docker - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add - - sudo apt-key fingerprint 0EBFCD88 - sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable" - sudo apt-get update - sudo apt-get install -y docker-ce docker-ce-cli containerd.io jq - # docker socket already works because OpenLab runs via root - - # Install yq + if ! command -v jq; + then + apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install -y jq + fi + GO111MODULE=on go get github.com/mikefarah/yq/v2 go get -u github.com/go-bindata/go-bindata/... - - source "${REPO_ROOT}/hack/ensure-kubectl.sh" - source "${REPO_ROOT}/hack/ensure-kind.sh" } # build kubernetes / node image, e2e binaries build() { + # possibly enable bazel build caching before building kubernetes + if [[ "${BAZEL_REMOTE_CACHE_ENABLED:-false}" == "true" ]]; then + create_bazel_cache_rcs.sh || true + fi + if [[ ! -d "$(go env GOPATH)/src/k8s.io/kubernetes" ]]; then - mkdir -p $(go env GOPATH)/src/k8s.io - cd $(go env GOPATH)/src/k8s.io + mkdir -p "$(go env GOPATH)/src/k8s.io" + cd "$(go env GOPATH)/src/k8s.io" git clone https://github.com/kubernetes/kubernetes.git cd kubernetes - git checkout -b ${KUBERNETES_VERSION} "refs/tags/${KUBERNETES_VERSION}" fi pushd "$(go env GOPATH)/src/k8s.io/kubernetes" + echo "Checking out Kubernetes version: ${KUBERNETES_VERSION}" + git checkout -b "${KUBERNETES_VERSION}" "refs/tags/${KUBERNETES_VERSION}" - # re-create _output/bin folder + # cleanup old _output/bin folder rm -rf "${PWD}/_output/bin" - mkdir -p "${PWD}/_output/bin/" - - go build -o ./_output/bin/kubectl ./cmd/kubectl - ./hack/generate-bindata.sh - go test -o ./_output/bin/e2e.test -c ./test/e2e/ + # make sure we have e2e requirements + make WHAT="test/e2e/e2e.test vendor/github.com/onsi/ginkgo/ginkgo cmd/kubectl" - go build -o ./_output/bin/ginkgo ./vendor/github.com/onsi/ginkgo/ginkgo - - PATH="$(go env GOPATH)/src/k8s.io/kubernetes/_output/bin:${PATH}" + # ensure the e2e script will find our binaries ... + PATH="$(dirname "$(find "${PWD}/_output/bin/" -name kubectl -type f)"):${PATH}" export PATH - popd # attempt to release some memory after building sync || true - sudo sh -c "echo 1 > /proc/sys/vm/drop_caches" || true + sh -c "echo 1 > /proc/sys/vm/drop_caches" || true + + popd - cd ${REPO_ROOT} + cd "${REPO_ROOT}" echo "Build Docker Images" make modules hack/tools/bin/clusterctl hack/tools/bin/kustomize hack/tools/bin/envsubst docker-build } @@ -280,17 +300,12 @@ E2E_CAPO_VERSION=0.4.0 # up a cluster with kind create_cluster() { - # actually create the cluster - KIND_IS_UP=true - # exports the b64 env vars used below - source ${REPO_ROOT}/templates/env.rc ${OPENSTACK_CLOUD_YAML_FILE} ${CLUSTER_NAME} - - cd ${REPO_ROOT} + cd "${REPO_ROOT}" # Create local repository to use local OpenStack provider and nightly CAPI mkdir -p ./out - cat ./hack/ci/e2e-conformance/clusterctl.yaml.tpl | \ + < ./hack/ci/e2e-conformance/clusterctl.yaml.tpl \ sed "s|\${PWD}|${PWD}|" | \ sed "s|\${E2E_CAPO_VERSION}|${E2E_CAPO_VERSION}|" | \ sed "s|\${E2E_CAPI_VERSION}|${E2E_CAPI_VERSION}|" \ @@ -332,7 +347,7 @@ create_cluster() { --worker-machine-count="${WORKER_MACHINE_COUNT}" > ./hack/ci/e2e-conformance/cluster.yaml # Patch cluster.yaml - cat ./hack/ci/e2e-conformance/e2e-conformance_patch.yaml.tpl | \ + < ./hack/ci/e2e-conformance/e2e-conformance_patch.yaml.tpl \ sed "s|\${OPENSTACK_CLOUD_PROVIDER_CONF_B64}|${OPENSTACK_CLOUD_PROVIDER_CONF_B64}|" | \ sed "s|\${OPENSTACK_CLOUD_CACERT_B64}|${OPENSTACK_CLOUD_CACERT_B64}|" | \ sed "s|\${USE_CI_ARTIFACTS}|${USE_CI_ARTIFACTS}|" | \ @@ -341,7 +356,8 @@ create_cluster() { sed "s|\${CLUSTER_NAME}|${CLUSTER_NAME}|" | \ sed "s|\${OPENSTACK_BASTION_MACHINE_FLAVOR}|${OPENSTACK_BASTION_MACHINE_FLAVOR}|" | \ sed "s|\${OPENSTACK_BASTION_IMAGE_NAME}|${OPENSTACK_BASTION_IMAGE_NAME}|" | \ - sed "s|\${OPENSTACK_SSH_KEY_NAME}|${OPENSTACK_SSH_KEY_NAME}|" \ + sed "s|\${OPENSTACK_SSH_KEY_NAME}|${OPENSTACK_SSH_KEY_NAME}|" | \ + sed "s|\${OPENSTACK_SSH_KEY_PUBLIC}|${OPENSTACK_SSH_KEY_PUBLIC}|" \ > ./hack/ci/e2e-conformance/e2e-conformance_patch.yaml ./hack/tools/bin/kustomize build --reorder=none hack/ci/e2e-conformance > ./out/cluster.yaml @@ -356,9 +372,10 @@ create_cluster() { # Delete already deployed provider set -x - ./hack/tools/bin/clusterctl delete --all + ./hack/tools/bin/clusterctl delete --all || true ./hack/tools/bin/clusterctl delete --infrastructure openstack --include-namespace --namespace capo-system || true - kubectl wait --for=delete --timeout=5m ns/capo-system || true + kubectl delete ns capi-kubeadm-bootstrap-system capi-kubeadm-control-plane-system capi-system || true + kubectl wait --for=delete --timeout=5m ns/capo-system ns/capi-kubeadm-bootstrap-system ns/capi-kubeadm-control-plane-system ns/capi-system || true # Deploy provider ./hack/tools/bin/clusterctl init --config ./out/clusterctl.yaml --infrastructure openstack --core cluster-api:v${E2E_CAPI_VERSION} --bootstrap kubeadm:v${E2E_CAPI_VERSION} --control-plane kubeadm:v${E2E_CAPI_VERSION} @@ -397,12 +414,14 @@ create_cluster() { attempt=0 while true; do kubectl -n "${CLUSTER_NAME}" get machines - read running total <<< $(kubectl -n "${CLUSTER_NAME}" get machines \ + # shellcheck disable=SC2046 + read -r running total <<< $(kubectl -n "${CLUSTER_NAME}" get machines \ -o json | jq -r '.items[].status.phase' | awk 'BEGIN{count=0} /(r|R)unning/{count++} END{print count " " NR}') ; - if [[ ${total} == ${running} ]]; then + if [[ ${total} == "${running}" ]]; then return 0 fi - read failed total <<< $(kubectl -n "${CLUSTER_NAME}" get machines \ + # shellcheck disable=SC2046 + read -r failed total <<< $(kubectl -n "${CLUSTER_NAME}" get machines \ -o json | jq -r '.items[].status.phase' | awk 'BEGIN{count=0} /(f|F)ailed/{count++} END{print count " " NR}') ; if [[ ! ${failed} -eq 0 ]]; then echo "$failed machines (out of $total) in cluster failed ... bailing out" @@ -467,7 +486,7 @@ run_tests() { (cd "$(go env GOPATH)/src/k8s.io/kubernetes" && ./hack/ginkgo-e2e.sh \ '--provider=skeleton' "--num-nodes=${NUM_NODES}" \ "--ginkgo.focus=${FOCUS}" "--ginkgo.skip=${SKIP}" \ - "--report-dir=${ARTIFACTS}" '--disable-log-dump=true' | tee ${ARTIFACTS}/e2e.log) + "--report-dir=${ARTIFACTS}" '--disable-log-dump=true' | tee "${ARTIFACTS}/e2e.log") unset KUBECONFIG unset KUBERNETES_CONFORMANCE_TEST @@ -480,8 +499,11 @@ main() { if [[ "$arg" == "--verbose" ]]; then set -o xtrace fi - if [[ "$arg" == "--install-prereqs" ]]; then - INSTALL_PREREQS="1" + if [[ "$arg" == "--skip-install-prereqs" ]]; then + SKIP_INSTALL_PREREQS="1" + fi + if [[ "$arg" == "--skip-build" ]]; then + SKIP_BUILD="1" fi if [[ "$arg" == "--skip-cleanup" ]]; then SKIP_CLEANUP="1" @@ -498,38 +520,50 @@ main() { if [[ "$arg" == "--run-tests-parallel" ]]; then export PARALLEL="true" fi - if [[ "$arg" == "--delete-cluster" ]]; then - DELETE_CLUSTER="1" + if [[ "$arg" == "--skip-delete-cluster" ]]; then + SKIP_DELETE_CLUSTER="1" fi done - # create temp dir and setup cleanup - SKIP_CLEANUP=${SKIP_CLEANUP:-""} - if [[ -z "${SKIP_CLEANUP}" ]]; then - trap dump_logs EXIT - fi # ensure artifacts exists when not in CI export ARTIFACTS mkdir -p "${ARTIFACTS}/logs" source "${REPO_ROOT}/hack/ensure-go.sh" source "${REPO_ROOT}/hack/ensure-kind.sh" + source "${REPO_ROOT}/hack/ensure-kubectl.sh" export GOPATH=${GOPATH:-/home/ubuntu/go} export PATH=$PATH:${GOPATH}/bin:/snap/bin:${HOME}/bin - INSTALL_PREREQS=${INSTALL_PREREQS:-""} - if [[ "${INSTALL_PREREQS}" == "yes" || "${INSTALL_PREREQS}" == "1" ]]; then + if [[ -z "${SKIP_INSTALL_PREREQS:-}" ]]; then echo "Install prereqs..." install_prereqs fi + + # setup cleanup + SKIP_CLEANUP=${SKIP_CLEANUP:-""} + if [[ -z "${SKIP_CLEANUP}" ]]; then + trap dump_logs EXIT + fi + + # exports the b64 env vars used below + # We also need CAPO_AUTH_URL to get files from the devstack later + source "${REPO_ROOT}"/templates/env.rc "${OPENSTACK_CLOUD_YAML_FILE}" "${CLUSTER_NAME}" + cp "${OPENSTACK_CLOUD_YAML_FILE}" ./ + export OS_CLOUD=${CLUSTER_NAME} + if [[ -z "${SKIP_UPLOAD_IMAGE:-}" ]]; then echo "Uploading image..." - upload_image ${OPENSTACK_IMAGE_NAME} ${IMAGE_URL} - upload_image ${OPENSTACK_BASTION_IMAGE_NAME} ${CIRROS_URL} + upload_image "${OPENSTACK_IMAGE_NAME}" "${IMAGE_URL}" + upload_image "${OPENSTACK_BASTION_IMAGE_NAME}" "${CIRROS_URL}" + fi + + if [[ -z "${SKIP_BUILD:-}" ]]; then + echo "Building..." + build fi - build create_key_pair create_cluster @@ -538,8 +572,7 @@ main() { run_tests fi - DELETE_CLUSTER=${DELETE_CLUSTER:-""} - if [[ "${DELETE_CLUSTER}" == "yes" || "${DELETE_CLUSTER}" == "1" ]]; then + if [[ -z "${SKIP_DELETE_CLUSTER:-}" ]]; then echo "Dumping logs" dump_logs diff --git a/hack/ci/e2e-conformance/e2e-conformance_patch.yaml.tpl b/hack/ci/e2e-conformance/e2e-conformance_patch.yaml.tpl index d616235819..361f136744 100644 --- a/hack/ci/e2e-conformance/e2e-conformance_patch.yaml.tpl +++ b/hack/ci/e2e-conformance/e2e-conformance_patch.yaml.tpl @@ -84,6 +84,14 @@ spec: echo "kubelet version: " $(kubelet --version) echo "$LINE_SEPARATOR" + users: + - name: "capi" + sudo: "ALL=(ALL) NOPASSWD:ALL" + # user: capi, passwd: capi + passwd: "$6$rounds=4096$yKTFKL6RmN128$a7cGMiNjeTSd091s6QzZcUNrMTgm3HhML5rVmpDFlCfgD7scTW7ZHr0OChcXCaeiO/kbhdn0XzIzWk63nSqRH1" + lockPassword: false + sshAuthorizedKeys: + - "${OPENSTACK_SSH_KEY_PUBLIC}" --- apiVersion: bootstrap.cluster.x-k8s.io/v1alpha4 kind: KubeadmConfigTemplate @@ -149,3 +157,11 @@ spec: echo "kubelet version: " $(kubelet --version) echo "$LINE_SEPARATOR" + users: + - name: "capi" + sudo: "ALL=(ALL) NOPASSWD:ALL" + # user: capi, passwd: capi + passwd: "$6$rounds=4096$yKTFKL6RmN128$a7cGMiNjeTSd091s6QzZcUNrMTgm3HhML5rVmpDFlCfgD7scTW7ZHr0OChcXCaeiO/kbhdn0XzIzWk63nSqRH1" + lockPassword: false + sshAuthorizedKeys: + - "${OPENSTACK_SSH_KEY_PUBLIC}" \ No newline at end of file diff --git a/hack/verify-boilerplate.sh b/hack/verify-boilerplate.sh index fc3d3ab7eb..ad20d4ea97 100755 --- a/hack/verify-boilerplate.sh +++ b/hack/verify-boilerplate.sh @@ -17,7 +17,8 @@ set -o errexit set -o nounset set -o pipefail -set -o verbose +# for debugging, enable verbose: +#set -o verbose KUBE_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. diff --git a/scripts/ci-conformance.sh b/scripts/ci-conformance.sh new file mode 100755 index 0000000000..896af7d713 --- /dev/null +++ b/scripts/ci-conformance.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# Copyright 2021 The Kubernetes Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +################################################################################ +# usage: ci-conformance.sh +# This program runs the clusterctl conformance e2e tests. +################################################################################ + +set -o nounset +set -o pipefail + +REPO_ROOT=$(dirname "${BASH_SOURCE[0]}")/.. +cd "${REPO_ROOT}" || exit 1 + +# shellcheck source=../hack/ensure-go.sh +source "${REPO_ROOT}/hack/ensure-go.sh" +# shellcheck source=../hack/ensure-kind.sh +source "${REPO_ROOT}/hack/ensure-kind.sh" +# shellcheck source=../hack/ensure-kubectl.sh +source "${REPO_ROOT}/hack/ensure-kubectl.sh" + +RESOURCE_TYPE="${RESOURCE_TYPE:-"gce-project"}" + +ARTIFACTS="${ARTIFACTS:-${PWD}/_artifacts}" +mkdir -p "${ARTIFACTS}/logs/" + +# our exit handler (trap) +cleanup() { + # stop boskos heartbeat + [[ -z ${HEART_BEAT_PID:-} ]] || kill -9 "${HEART_BEAT_PID}" + + # will be started by e2e-conformance-gcp-prepare.sh + pkill sshuttle +} +trap cleanup EXIT + +#Install requests module explicitly for HTTP calls +python3 -m pip install requests + +# If BOSKOS_HOST is set then acquire an GCP account from Boskos. +if [ -n "${BOSKOS_HOST:-}" ]; then + # Check out the account from Boskos and store the produced environment + # variables in a temporary file. + account_env_var_file="$(mktemp)" + python3 hack/boskos.py --get --resource-type="${RESOURCE_TYPE}" 1>"${account_env_var_file}" + checkout_account_status="${?}" + + # If the checkout process was a success then load the account's + # environment variables into this process. + # shellcheck disable=SC1090 + [ "${checkout_account_status}" = "0" ] && . "${account_env_var_file}" + + # Always remove the account environment variable file. It contains + # sensitive information. + rm -f "${account_env_var_file}" + + if [ ! "${checkout_account_status}" = "0" ]; then + echo "error getting account from boskos" 1>&2 + exit "${checkout_account_status}" + fi + + # run the heart beat process to tell boskos that we are still + # using the checked out account periodically + python3 -u hack/boskos.py --heartbeat >> "$ARTIFACTS/logs/boskos.log" 2>&1 & + HEART_BEAT_PID=$! +fi + +hack/ci/e2e-conformance-gcp-prepare.sh + +export ARTIFACTS +export OPENSTACK_DNS_NAMESERVERS=8.8.8.8 +export CONTROL_PLANE_MACHINE_COUNT=1 +export WORKER_MACHINE_COUNT=4 +hack/ci/e2e-conformance.sh --run-tests-parallel --verbose $* +test_status="${?}" + +# If Boskos is being used then release the GCP project back to Boskos. +[ -z "${BOSKOS_HOST:-}" ] || python3 hack/boskos.py --release >> "$ARTIFACTS/logs/boskos.log" 2>&1 + +exit "${test_status}"