Skip to content

Use nodeenv to manage node version in Jenkins builds - #13911

Merged
benpatterson merged 1 commit into
masterfrom
benp/nodeenv
Nov 10, 2016
Merged

Use nodeenv to manage node version in Jenkins builds#13911
benpatterson merged 1 commit into
masterfrom
benp/nodeenv

Conversation

@benpatterson

@benpatterson benpatterson commented Nov 4, 2016

Copy link
Copy Markdown
Contributor

This pull request

This pull request does not:

  • Attempt to solve any stability issues in builds related to node.
  • Make node installs any faster
  • Create any caching infrastructure to speed up node installs. They will be approximately as fast as they were before. Downloading the node bits build-to-build adds about 3 seconds, which is worth it for the added flexibility.

@benpatterson

Copy link
Copy Markdown
Contributor Author

@e0d @edx/testeng This is a WIP but I'd appreciate your feedback, esp in case there's anything about this overall approach that sticks out

@benpatterson benpatterson changed the title nodeenv WIP Use nodeenv Nov 8, 2016
@benpatterson

Copy link
Copy Markdown
Contributor Author

@maxrothman as well

I'm pulling this out of 'wip' and into 'for real'. Please consider; open to feedback.

@maxrothman

Copy link
Copy Markdown
Contributor

Looks good to me. 👍

@benpatterson benpatterson changed the title Use nodeenv Use nodeenv for manage node version in Jenkins builds Nov 8, 2016
@bjacobel

bjacobel commented Nov 9, 2016

Copy link
Copy Markdown
Contributor

This will indeed be very helpful when we upgrade node in platform.

Strictly a feature request/Q: does anybody know what happens if we pass a non-specific version to nodeenv? E.g., 6.9, or even just 6? Can we get upgrades without needing to bump the version in
jenkins-common.sh?

👍

@benpatterson

benpatterson commented Nov 9, 2016

Copy link
Copy Markdown
Contributor Author

@bjacobel The short answer is: a 404.

For posterity, two outputs:

\1. When specifying a non-existent node version:

$ nodeenv --node=100 --prebuilt node_100 --force
 * Install prebuilt node (100) .Traceback (most recent call last):
  File "/workspaces/foo/venv/bin/nodeenv", line 11, in <module>
    sys.exit(main())
  File "/workspaces/foo/venv/lib/python2.7/site-packages/nodeenv.py", line 927, in main
    create_environment(env_dir, opt)
  File "/workspaces/foo/venv/lib/python2.7/site-packages/nodeenv.py", line 768, in create_environment
    install_node(env_dir, src_dir, opt)
  File "/workspaces/foo/venv/lib/python2.7/site-packages/nodeenv.py", line 640, in install_node
    download_node_src(node_url, src_dir, opt, prefix)
  File "/workspaces/foo/venv/lib/python2.7/site-packages/nodeenv.py", line 527, in download_node_src
    tar_contents = io.BytesIO(urlopen(node_url).read())
  File "/workspaces/foo/venv/lib/python2.7/site-packages/nodeenv.py", line 545, in urlopen
    return urllib2.urlopen(req)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 154, in urlopen
    return opener.open(url, data, timeout)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 437, in open
    response = meth(req, response)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 550, in http_response
    'http', request, response, code, msg, hdrs)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 475, in error
    return self._call_chain(*args)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 409, in _call_chain
    result = func(*args)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 558, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 404: Not Found

\2. A generic version number:

$ nodeenv --node=6 --prebuilt node_6 --force
 * Install prebuilt node (6) .Traceback (most recent call last):
  File "/workspaces/foo/venv/bin/nodeenv", line 11, in <module>
    sys.exit(main())
  File "/workspaces/foo/venv/lib/python2.7/site-packages/nodeenv.py", line 927, in main
    create_environment(env_dir, opt)
  File "/workspaces/foo/venv/lib/python2.7/site-packages/nodeenv.py", line 768, in create_environment
    install_node(env_dir, src_dir, opt)
  File "/workspaces/foo/venv/lib/python2.7/site-packages/nodeenv.py", line 640, in install_node
    download_node_src(node_url, src_dir, opt, prefix)
  File "/workspaces/foo/venv/lib/python2.7/site-packages/nodeenv.py", line 527, in download_node_src
    tar_contents = io.BytesIO(urlopen(node_url).read())
  File "/workspaces/foo/venv/lib/python2.7/site-packages/nodeenv.py", line 545, in urlopen
    return urllib2.urlopen(req)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 154, in urlopen
    return opener.open(url, data, timeout)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 437, in open
    response = meth(req, response)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 550, in http_response
    'http', request, response, code, msg, hdrs)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 475, in error
    return self._call_chain(*args)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 409, in _call_chain
    result = func(*args)
  File "/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py", line 558, in http_error_default
    raise HTTPError(req.get_full_url(), code, msg, hdrs, fp)
urllib2.HTTPError: HTTP Error 404: Not Found

@benpatterson benpatterson changed the title Use nodeenv for manage node version in Jenkins builds Use nodeenv to manage node version in Jenkins builds Nov 9, 2016
@benpatterson

Copy link
Copy Markdown
Contributor Author

got the cb never called error.

jenkins run python

Comment thread scripts/jenkins-common.sh Outdated
# Ensure we are starting with a clean node env directory
rm -rf $NODE_ENV_DIR
WAIT_COUNT=0
until timeout doCreateNodeEnv || [ $WAIT_COUNT -eq 3 ]; do

@pwnage101 pwnage101 Nov 9, 2016

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

if doCreateNodeEnv itself exits with nonzero status, this loop would assume a timeout. May need to restructure the loop to accomodate for that detail. timeout(1) will exit with status 127 124 on timeout, which is what you're really looking for.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

refresh page just in case, i made an important typo fix in the above comment :)

@benpatterson

Copy link
Copy Markdown
Contributor Author

cb() never called error; flaky JS test

jenkins run js
jenkins run quality

@benpatterson

Copy link
Copy Markdown
Contributor Author

jenkins run bokchoy

@benpatterson

Copy link
Copy Markdown
Contributor Author

@pwnage101 could you suggest an implementation that would trap the exit code in the case of a timeout vs not-a-timeout?

Depending on how easy/hard that is to accomplish, we could consider shipping this as-is or refactoring.

@pwnage101

Copy link
Copy Markdown
Contributor

Quoting Ben Patterson (2016-11-09 21:24:46)

@pwnage101 could you suggest an implementation that would trap the
exit code in the case of a timeout vs not-a-timeout?

Something like this should work:

EXIT_STATUS = 0
TIMEOUT_STATUS = 124 # timed-out status, see man 1 timeout
WAIT_COUNT = 0
WAIT_LIMIT = 3
unless [ $EXIT_STATUS -eq $TIMEOUT_STATUS -o $WAIT_COUNT -eq $WAIT_LIMIT ]; do
timeout 30s $NODE_INSTALL_COMMAND
exit_status = $?
retries=$(( retries++ ))
sleep 5
done

Troy

@pwnage101

Copy link
Copy Markdown
Contributor

oops, bugs and typos on so many levels. Let me try again...

TIMEOUT_STATUS = 124  # timed-out status, see `man 1 timeout`
EXIT_STATUS = $TIMEOUT_STATUS
WAIT_COUNT = 0
WAIT_LIMIT = 3
# two conditions for breaking out of the retry loop: (1) we didn't timeout, or
# (2) we reached the retry limit.
until [ $EXIT_STATUS -neq $TIMEOUT_STATUS -o $WAIT_COUNT -eq $WAIT_LIMIT ]; do
  timeout 30s $NODE_INSTALL_COMMAND
  EXIT_STATUS = $?
  WAIT_COUNT=$(( WAIT_COUNT++ ))
  sleep 5
done

Comment thread scripts/jenkins-common.sh Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This condition shouldn't be removed, I don't think. Without it, nothing gets done on max retries.

@benpatterson

Copy link
Copy Markdown
Contributor Author

@pwnage101 Thanks for the help, but it never quite behaved as intended. I think because the exit status was captured in the pipe, it wasn't registering correctly (i.e., because it was technically still happening). I did a fair amount of testing on a Jenkins box but in the end wasn't able to make it that smart.

So I'm leaving the older implementation...wherein it'll exit 1 whether it's a timeout or an actual command failure.

@benpatterson

Copy link
Copy Markdown
Contributor Author

Oh also I'm intentionally leaving a unique echo statement in there so we can hit splunk and see if this is catching an intermittent hang.

@pwnage101

Copy link
Copy Markdown
Contributor

The less fun implementation will likely work for our use case, but keep in mind that a command failure will result in retries which hopefully are not bad to perform.

@benpatterson

benpatterson commented Nov 10, 2016

Copy link
Copy Markdown
Contributor Author

@pwnage101 you are right.

I figure retrying a failed command is better than risking occasional hangs. I was about to merge this yesterday when I ran into a hang. It was the first I'd seen out of a couple or few dozen builds, but that could amount to another flaky condition and I wanted to (attempt to) nip it in the bud.

I gave a smarter implementation a go, but after a couple of hours working on it, I'm going to let it go for now. Not sure what the emoticon is for that...I'll say 🐂 as a hard working, albeit slow-moving animal. (er, that's meant to reflect the implementation, not me!)

@pwnage101

Copy link
Copy Markdown
Contributor

makes sense.

👍 provided that you add a comment like "command will also be retried on failure, even if it didn't timeout"

@benpatterson

Copy link
Copy Markdown
Contributor Author

Done.

I'll let this run, then squash and run again, and merge at that point unless we discover something else.

@benpatterson

Copy link
Copy Markdown
Contributor Author

github failures...

jenkins run lettuce

This change:
* gives us the flexibility of managing node from pull-request to pull-request. It'll be a huge lift to folks that want to test out node upgrades like @bjacobel or @andy-armstrong
* Sets us up for a port to Xenial, which will be accomplished in part by using nodeenv for managing the node environment. See a companion PR on the configuration repo for edxapp: edx/configuration#3444

This pull request does not:
* Attempt to solve any stability issues in builds related to node.
* Make node installs any faster
* Create any caching infrastructure to speed up node installs. They will be approximately as fast as they were before. Downloading the node bits build-to-build adds about 3 seconds, which is worth it for the added flexibility.
@benpatterson

benpatterson commented Nov 10, 2016

Copy link
Copy Markdown
Contributor Author

jenkins run bokchoy

ref: https://openedx.atlassian.net/browse/FEDX-99

@benpatterson
benpatterson merged commit 562b48c into master Nov 10, 2016
@benpatterson
benpatterson deleted the benp/nodeenv branch November 10, 2016 17:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants