Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use ssh to send image instead of http to avoid timeouts #1087

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion core/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ ENV DEBIAN_FRONTEND noninteractive
# https://github.com/balena-io/balena-cli/blob/master/INSTALL-LINUX.md#additional-dependencies
# hadolint ignore=DL3008
RUN apt-get update && apt-get install --no-install-recommends -y \
bind9-dnsutils \
bind9-dnsutils gzip\
ca-certificates \
docker.io \
git \
Expand Down
124 changes: 56 additions & 68 deletions core/lib/common/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,72 +135,60 @@ module.exports = class Worker {
* @category helper
*/
async flash(imagePath) {
let attempt = 0;
await retry(
async () => {
attempt++;
this.logger.log(`Preparing to flash, attempt ${attempt}...`);

await new Promise(async (resolve, reject) => {
const req = rp.post({ uri: `${this.url}/dut/flash`, timeout: 0 });

req.catch((error) => {
this.logger.log(`client side error: `)
this.logger.log(error.message)
reject(error);
});
req.finally(() => {
if (lastStatus !== 'done') {
reject(new Error('Unexpected end of TCP connection'));
}

resolve();
});

let lastStatus;
req.on('data', (data) => {
const computedLine = RegExp('(.+?): (.*)').exec(data.toString());

if (computedLine) {
if (computedLine[1] === 'error') {
req.cancel();
reject(new Error(computedLine[2]));
}

if (computedLine[1] === 'progress') {
once(() => {
this.logger.log('Flashing');
});
// Hide any errors as the lines we get can be half written
const state = JSON.parse(computedLine[2]);
if (state != null && isNumber(state.percentage)) {
this.logger.status({
message: 'Flashing',
percentage: state.percentage,
});
}
}

if (computedLine[1] === 'status') {
lastStatus = computedLine[2];
}
}
});

pipeline(
fs.createReadStream(imagePath),
createGzip({ level: 6 }),
req,
);
});
this.logger.log('Flash completed');
},
{
max_tries: 5,
interval: 1000 * 5,
throw_original: true,
},
);

let TARGET_PATH = imagePath;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
let TARGET_PATH = imagePath;
let targetPath = imagePath;

Can we change this everywhere to follow the same naming conventions.

// if using remote worker i.e autokit/testbot, send the image over ssh
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// if using remote worker i.e autokit/testbot, send the image over ssh
// if autokit/testbot, send the image over ssh

Clarity++, saying remote worker adds another shade of terminology

if(!this.directConnect){
this.logger.log(`Gzipping image...`);
await exec(`gzip ${imagePath}`);
// adjust image path to take new gz suffix
imagePath = `${imagePath}.gz`
// ensure we always send to the same location on worker to avoid overflowing of images
TARGET_PATH = `/data/os.img.gz`

// Wrap sending of image in a retry - hopefully the --partial arguement in the rsync command means it will resume
// in the case of an error
Comment on lines +149 to +150
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Wrap sending of image in a retry - hopefully the --partial arguement in the rsync command means it will resume
// in the case of an error
// Send image with rsync command, retry if error

await retry(
async () => {
this.logger.log(`Sending image ${imagePath}`);
try{
// arrives at worker as /data/os.img.gz
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// arrives at worker as /data/os.img.gz
// Sends image to worker at TARGET_PATH

await this.sendFile(imagePath, TARGET_PATH, 'worker');
}catch(e){
console.log(e);
throw new Error(`Rysnc error: ${e.message}`)
Comment on lines +158 to +159
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
console.log(e);
throw new Error(`Rysnc error: ${e.message}`)
throw new Error(`Rysnc error: ${e}`)

}
},
{
max_tries: 5,
interval: 1000 * 5,
throw_original: true
}
)
}

// note: for the qemu case, we are assuming a shared volume between core and worker - this means that any image we want to flash must be in this volume - is it in all our cases?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Who is this question for?
If you are asking me then yeah why not. The docker-compose will define the volumes once for QEMU and that should be the case everywhere no?

this.logger.log(`Trying to flash ${TARGET_PATH}`);
await new Promise(async (resolve, reject) => {
let res = rp.post(
{
uri: `${this.url}/dut/flashFromFile`,
body: {
filename: TARGET_PATH
},
timeout: 0,
json: true
}
);

req.on('data', (data) => {
this.logger.log(data.toString());
});

req.finally(() => {
resolve();
});
});
}


Expand Down Expand Up @@ -522,12 +510,12 @@ module.exports = class Worker {
);
// todo : replace with npm package
await exec(
`rsync -av -e "ssh ${this.workerUser}@${this.workerHost} -p ${this.workerPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -q ${this.sshPrefix}balena exec -i" ${filePath} ${containerId}:${destination}`,
`rsync -av --partial -e "ssh ${this.workerUser}@${this.workerHost} -p ${this.workerPort} -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -q ${this.sshPrefix}balena exec -i" ${filePath} ${containerId}:${destination}`,
);
} else {
let ip = await this.ip(target);
await exec(
`rsync -av -e "ssh -p 22222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -q -i ${this.sshKey}" ${filePath} root@${ip}:${destination}`,
`rsync -av --partial -e "ssh -p 22222 -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -q -i ${this.sshKey}" ${filePath} root@${ip}:${destination}`,
);
}
}
Expand Down
Loading