def _get_rook_files(self): # TODO (bleon) # This is not optima. Need to retrieve RPM directly and extract files # out of it. RPM URL should be configurable execute(f"rsync -avr -e 'ssh -i {self.workspace.private_key}'" f" sles@{self.kubernetes.hardware.masters[0].get_ssh_ip()}" f":/usr/share/k8s-yaml/rook {self.workspace.working_dir}")
def _cloud_init_seed_create(self): user_data = textwrap.dedent(""" #cloud-config debug: True ssh_authorized_keys: - {} """) meta_data = textwrap.dedent(""" --- instance-id: {} local-hostname: {} """) iso_cmd = shutil.which('mkisofs') if not iso_cmd: raise Exception('mkisofs command not found') if os.path.exists(self._cloud_init_seed_path): os.remove(self._cloud_init_seed_path) with tempfile.TemporaryDirectory() as tempdir: with open(os.path.join(tempdir, 'user-data'), 'w') as ud: ud.write(user_data.format(self._ssh_public_key)) with open(os.path.join(tempdir, 'meta-data'), 'w') as md: md.write(meta_data.format(uuid.uuid4(), self.name)) # create the seed file args = [ iso_cmd, '-output', self._cloud_init_seed_path, '-volid', 'cidata', '-joliet', '-rock', tempdir ] execute(" ".join(args), log_stdout=False, log_stderr=False)
def _backing_file_create(self): if os.path.exists(self._snap_img_path): logger.info(f"node {self.name}: Delete available backing image " f"{self._snap_img_path}") os.remove(self._snap_img_path) execute(f"qemu-img create -f qcow2 -F qcow2 -o " f"backing_file={self._image_path} {self._snap_img_path} 10G") logger.info(f"node {self.name}: created qcow2 backing file under" f"{self._snap_img_path}")
def get_rook(self): logger.info("Clone rook version %s from repo %s" % ( settings.UPSTREAM_ROOK.VERSION, settings.UPSTREAM_ROOK.REPO)) execute( "git clone -b %s %s %s" % ( settings.UPSTREAM_ROOK.VERSION, settings.UPSTREAM_ROOK.REPO, self.build_dir), log_stderr=False )
def get_rook(self): if not converter('@bool', settings.UPSTREAM_ROOK.BUILD_ROOK_FROM_GIT): return super().get_rook() logger.info( "Clone rook version %s from repo %s" % (settings.UPSTREAM_ROOK.VERSION, settings.UPSTREAM_ROOK.REPO)) execute("git clone -b %s %s %s" % (settings.UPSTREAM_ROOK.VERSION, settings.UPSTREAM_ROOK.REPO, self.build_dir), log_stderr=False)
def _get_rook_yaml(self): # do not use rpm-package for self-built rook images if converter('@bool', settings.SES.BUILD_ROOK_FROM_GIT): self.ceph_dir = os.path.join( self.build_dir, 'cluster/examples/kubernetes/ceph') return # TODO (bleon) # This is not optima. Need to retrieve RPM directly and extract files # out of it. RPM URL should be configurable execute(f"rsync -avr -e 'ssh -i {self.workspace.private_key}'" f" {settings.NODE_IMAGE_USER}" f"@{self.kubernetes.hardware.masters[0].get_ssh_ip()}" f":/usr/share/k8s-yaml/rook {self.workspace.working_dir}")
def disk_create(self, capacity): """ Create a disk volume """ super().disk_create(capacity) capacity_gb = f"{capacity}G" suffix = ''.join( random.choice(string.ascii_lowercase) for i in range(5)) name = f"{self._name}-volume-{suffix}" disk_path = os.path.join(self._workspace.working_dir, f"{name}.qcow2") execute(f"qemu-img create -f qcow2 {disk_path} {capacity_gb}") self._disks[name] = {'path': disk_path, 'attached': False, 'xml': None} logger.info(f"disk {name} created") return name
def execute( self, command: str, capture: bool = False, check: bool = True, log_stdout: bool = True, log_stderr: bool = True, env: Optional[Dict[str, str]] = None, logger_name: Optional[str] = None, chdir: Optional[str] = None ) -> Tuple[int, Optional[str], Optional[str]]: """Executes a command inside the workspace This is a wrapper around the execute util that will automatically chdir into the workspace and set some common env vars (such as the ssh agent). """ if not env: env = { 'PATH': os.environ.get('PATH', '/usr/local/bin:/usr/bin:/bin') } env['PATH'] = f"{os.path.join(self.working_dir, 'bin')}:{env['PATH']}" with self.chdir(chdir): env['SSH_AUTH_SOCK'] = self.ssh_agent_auth_sock env['SSH_AGENT_PID'] = self.ssh_agent_pid return execute(command, capture=capture, check=check, log_stdout=log_stdout, log_stderr=log_stderr, env=env, logger_name=logger_name)
def _check_docker_requirement(): logger.debug("Checking if docker is running...") if settings.DISTRO == 'openSUSE_k8s' and \ converter('@bool', settings.UPSTREAM_ROOK.BUILD_ROOK_FROM_GIT): rc, out, err = common.execute('docker ps', log_stdout=False) if rc != 0: raise Exception("Docker is not running - see manual.") logger.debug("... Docker appears to be ready")
def add_data_disk(self, capacity='10G'): _id = len(self._disks) + 2 # one for root disk, one for cloud-init volume_name = f'data-{_id}' block_device = f'vd{string.ascii_lowercase[_id + 1]}' disk_path = os.path.join(self._workspace.working_dir, f"{self.name}-{volume_name}.qcow2") execute(f"qemu-img create -f qcow2 {disk_path} {capacity}") logger.info(f'Created volume "{volume_name}" / size={capacity}') disk = textwrap.dedent(""" <disk type='file' device='disk'> <driver name='qemu' type='qcow2' cache='none'/> <source file='%(disk_path)s'/> <target dev='%(block_device)s' bus='virtio'/> </disk> """ % { "disk_path": disk_path, "block_device": block_device }) self._dom.attachDevice(disk) self._disks.append(disk_path)
def build(self): super().build() self.get_rook() if not converter('@bool', settings.UPSTREAM_ROOK.BUILD_ROOK_FROM_GIT): return self.get_golang() logger.info("Compiling rook...") execute( command=f"make --directory {self.build_dir} " f"-j BUILD_REGISTRY='rook-build' IMAGES='ceph'", env={"PATH": f"{self.workspace.bin_dir}/go/bin:" f"{os.environ['PATH']}", "TMPDIR": self.workspace.tmp_dir, "GOCACHE": self.workspace.tmp_dir, "GOPATH": self.workspace.build_dir}, log_stderr=False) image = 'rook/ceph' tag = f"{settings.UPSTREAM_ROOK.VERSION}-rookcheck" self.rook_image = f"{image}:{tag}" logger.info(f"Tag image as {image}:{tag}") execute(f'docker tag "rook-build/ceph-amd64" {image}:{tag}') logger.info("Save image tar") # TODO(jhesketh): build arch may differ execute(f"docker save {image}:{tag} | gzip > %s" % os.path.join(self.build_dir, 'rook-ceph.tar.gz')) self._rook_built = True
def kubectl(self, command, check=True, log_stdout=True, log_stderr=True): """ Run a kubectl command """ return common.execute( f"{self.kubectl_exec} --kubeconfig {self.kubeconfig}" f" {command}", check=check, capture=True, log_stdout=log_stdout, log_stderr=log_stderr, logger_name=f"kubectl {command}", )
def helm(self, command, check=True, log_stdout=True, log_stderr=True): """ Run a helm command """ return common.execute( f"{self.helm_exec} --kubeconfig {self.kubeconfig}" f" {command}", check=check, capture=True, log_stdout=log_stdout, log_stderr=log_stderr, logger_name=f"helm {command}", env={'HELM_EXPERIMENTAL_OCI': '1'}, )
def _ssh_agent(self): try: # NOTE(jhesketh): We can't use self.execute yet because # self.ssh_agent_pid is not ready yet. rc, stdout, stderr = execute( f'ssh-agent -a {self.ssh_agent_auth_sock}', capture=True) except subprocess.CalledProcessError: logger.exception('Failed to start ssh agent') raise self._ssh_agent_pid = stdout.split(';')[2].split('=')[1] try: logging.info("Adding ssh-key to agent") # NOTE(jhesketh): For some reason, ssh-add outputs to stderr which # will be logged as a warning. It's not really # dangerous because we're creating and destroying # our own agent, so we'll suppress the messages. self.execute(f'ssh-add {self.private_key}', log_stderr=False) except subprocess.CalledProcessError: logger.exception('Failed to add keys to agent') raise
def build(self): self.get_rook() self.get_golang() logger.info("Compiling rook...") execute( command=f"make --directory {self.build_dir} " f"-j BUILD_REGISTRY='rook-build' IMAGES='ceph'", env={"PATH": f"{self.workspace.bin_dir}/go/bin:" f"{os.environ['PATH']}", "TMPDIR": self.workspace.tmp_dir, "GOCACHE": self.workspace.tmp_dir, "GOPATH": self.workspace.build_dir}, log_stderr=False) logger.info(f"Tag image as {self.rook_image}") execute(f'docker tag "rook-build/ceph-amd64" {self.rook_image}') logger.info("Save image tar") # TODO(jhesketh): build arch may differ execute(f"docker save {self.rook_image} | gzip > %s" % os.path.join(self.build_dir, 'rook-ceph.tar.gz')) self._rook_built = True
def build_rook(self): logger.info("[build_rook] Download go") wget.download( "https://dl.google.com/go/go1.13.9.linux-amd64.tar.gz", os.path.join(self.builddir, 'go-amd64.tar.gz'), bar=None, ) logger.info("[build_rook] Unpack go") execute( "tar -C %s -xzf %s" % (self.builddir, os.path.join(self.builddir, 'go-amd64.tar.gz'))) # TODO(jhesketh): Allow setting rook version logger.info("[build_rook] Checkout rook") execute("mkdir -p %s" % os.path.join(self.builddir, 'src/github.com/rook/rook')) execute("git clone https://github.com/rook/rook.git %s" % os.path.join(self.builddir, 'src/github.com/rook/rook'), log_stderr=False) # TODO(jhesketh): Allow testing various versions of rook execute("pushd %s && git checkout v1.3.1 && popd" % os.path.join(self.builddir, 'src/github.com/rook/rook'), log_stderr=False) logger.info("[build_rook] Make rook") execute( "PATH={builddir}/go/bin:$PATH GOPATH={builddir} " "make --directory='{builddir}/src/github.com/rook/rook' " "-j BUILD_REGISTRY='rook-build' IMAGES='ceph' " "build".format(builddir=self.builddir), log_stderr=False, logger_name="make -j BUILD_REGISTRY='rook-build' IMAGES='ceph'", ) logger.info("[build_rook] Tag image") execute('docker tag "rook-build/ceph-amd64" rook/ceph:master') logger.info("[build_rook] Save image tar") # TODO(jhesketh): build arch may differ execute("docker save rook/ceph:master | gzip > %s" % os.path.join(self.builddir, 'rook-ceph.tar.gz')) self.ceph_dir = os.path.join( self.builddir, 'src/github.com/rook/rook/cluster/examples/kubernetes/ceph') self._rook_built = True
def test_command_matrix(log_stdout, log_stderr, capture, check, fail_command, logger_name, caplog): # Capturing behaves differently depending if logging is enabled or an error # is raised, so test with a complete matrix of log_stdout/err. # Force caplog to INFO level so that we get what we expect caplog.set_level(logging.INFO) cmd = 'echo "Hello world" && >&2 echo "error"' expected_rc = 0 if fail_command: expected_rc = 30 cmd += f" && exit {expected_rc}" try: rc, stdout, stderr = execute(cmd, capture=capture, log_stdout=log_stdout, log_stderr=log_stderr, check=check, logger_name=logger_name) except subprocess.CalledProcessError as exception: if check: # We have to get the return values from the exception rc = exception.returncode stdout = exception.stdout stderr = exception.stderr else: assert False, "No error should have been raised with check=False!" assert rc == expected_rc if capture: assert stdout == "Hello world\n" assert stderr == "error\n" else: assert stdout is None assert stderr is None logger_name_check = logger_name if logger_name is not None else cmd if log_stdout and log_stderr: assert len(caplog.records) == 2 for record in caplog.records: if record.levelname == 'INFO': assert record.name == logger_name_check assert record.levelname == 'INFO' assert record.getMessage() == 'Hello world' else: assert record.name == logger_name_check assert record.levelname == 'WARNING' assert record.getMessage() == 'error' elif log_stdout: assert len(caplog.records) == 1 assert caplog.records[0].name == logger_name_check assert caplog.records[0].levelname == 'INFO' assert caplog.records[0].getMessage() == 'Hello world' elif log_stderr: assert len(caplog.records) == 1 assert caplog.records[0].name == logger_name_check assert caplog.records[0].levelname == 'WARNING' assert caplog.records[0].getMessage() == 'error'
def test_command_env(): rc, stdout, stderr = execute("echo $MYVAR", env={'MYVAR': "Hello world"}, capture=True) assert stdout == "Hello world\n" assert stderr == ""