Use OSCaaS to speed up devstack runs
OpenStackClient has a significant amount of startup overhead, which adds a non-trivial amount of time to each devstack run because it makes a lot of OSC calls. This change uses the OSC service from [0] to run a persistent process that handles openstack calls. This removes most of the startup overhead and in my local testing removes about three minutes per devstack run. Currently this is implemented as an opt-in feature. There are likely a lot of edge cases in projects that use a devstack plugin so turning it on universally is going to require boiling the ocean. I think getting this in and enabled for some of the major projects should give us a lot of the benefit without the enormous effort of making it 100% compatible across all of OpenStack. Depends-On: https://review.opendev.org/c/openstack/nova/+/918689 Depends-On: https://review.opendev.org/c/openstack/ironic/+/918690 Change-Id: I28e6159944746abe2d320369249b87f1c4b9e24e 0: http://lists.openstack.org/pipermail/openstack-dev/2016-April/092546.html
This commit is contained in:
parent
951e53bfcc
commit
9a97326c3f
119
files/openstack-cli-server/openstack
Executable file
119
files/openstack-cli-server/openstack
Executable file
@ -0,0 +1,119 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# 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 socket
|
||||
import sys
|
||||
import os
|
||||
import os.path
|
||||
import json
|
||||
|
||||
server_address = "/tmp/openstack.sock"
|
||||
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
|
||||
try:
|
||||
sock.connect(server_address)
|
||||
except socket.error as msg:
|
||||
print(msg, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def send(sock, doc):
|
||||
jdoc = json.dumps(doc)
|
||||
sock.send(b'%d\n' % len(jdoc))
|
||||
sock.sendall(jdoc.encode('utf-8'))
|
||||
|
||||
def recv(sock):
|
||||
length_str = b''
|
||||
|
||||
char = sock.recv(1)
|
||||
if len(char) == 0:
|
||||
print("Unexpected end of file", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
while char != b'\n':
|
||||
length_str += char
|
||||
char = sock.recv(1)
|
||||
if len(char) == 0:
|
||||
print("Unexpected end of file", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
total = int(length_str)
|
||||
|
||||
# use a memoryview to receive the data chunk by chunk efficiently
|
||||
jdoc = memoryview(bytearray(total))
|
||||
next_offset = 0
|
||||
while total - next_offset > 0:
|
||||
recv_size = sock.recv_into(jdoc[next_offset:], total - next_offset)
|
||||
next_offset += recv_size
|
||||
try:
|
||||
doc = json.loads(jdoc.tobytes())
|
||||
except (TypeError, ValueError) as e:
|
||||
raise Exception('Data received was not in JSON format')
|
||||
return doc
|
||||
|
||||
try:
|
||||
env = {}
|
||||
passenv = ["CINDER_VERSION",
|
||||
"OS_AUTH_URL",
|
||||
"OS_IDENTITY_API_VERSION",
|
||||
"OS_NO_CACHE",
|
||||
"OS_PASSWORD",
|
||||
"OS_PROJECT_NAME",
|
||||
"OS_REGION_NAME",
|
||||
"OS_TENANT_NAME",
|
||||
"OS_USERNAME",
|
||||
"OS_VOLUME_API_VERSION",
|
||||
"OS_CLOUD"]
|
||||
for name in passenv:
|
||||
if name in os.environ:
|
||||
env[name] = os.environ[name]
|
||||
|
||||
cmd = {
|
||||
"app": os.path.basename(sys.argv[0]),
|
||||
"env": env,
|
||||
"argv": sys.argv[1:]
|
||||
}
|
||||
try:
|
||||
image_idx = sys.argv.index('image')
|
||||
create_idx = sys.argv.index('create')
|
||||
missing_file = image_idx < create_idx and \
|
||||
not any(x.startswith('--file') for x in sys.argv)
|
||||
except ValueError:
|
||||
missing_file = False
|
||||
|
||||
if missing_file:
|
||||
# This means we were called with an image create command, but were
|
||||
# not provided a --file option. That likely means we're being passed
|
||||
# the image data to stdin, which won't work because we do not proxy
|
||||
# stdin to the server. So, we just reject the operation and ask the
|
||||
# caller to provide the file with --file instead.
|
||||
# We've already connected to the server, we need to send it some dummy
|
||||
# data so it doesn't wait forever.
|
||||
send(sock, {})
|
||||
print('Image create without --file is not allowed in server mode',
|
||||
file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
send(sock, cmd)
|
||||
|
||||
doc = recv(sock)
|
||||
if doc["stdout"] != b'':
|
||||
print(doc["stdout"], end='')
|
||||
if doc["stderr"] != b'':
|
||||
print(doc["stderr"], file=sys.stderr)
|
||||
sys.exit(doc["status"])
|
||||
finally:
|
||||
sock.close()
|
118
files/openstack-cli-server/openstack-cli-server
Executable file
118
files/openstack-cli-server/openstack-cli-server
Executable file
@ -0,0 +1,118 @@
|
||||
#!/usr/bin/env python3
|
||||
# Copyright 2016 Red Hat, Inc.
|
||||
#
|
||||
# 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 socket
|
||||
import sys
|
||||
import os
|
||||
import json
|
||||
|
||||
from openstackclient import shell as osc_shell
|
||||
from io import StringIO
|
||||
|
||||
server_address = "/tmp/openstack.sock"
|
||||
|
||||
try:
|
||||
os.unlink(server_address)
|
||||
except OSError:
|
||||
if os.path.exists(server_address):
|
||||
raise
|
||||
|
||||
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
||||
print('starting up on %s' % server_address, file=sys.stderr)
|
||||
sock.bind(server_address)
|
||||
|
||||
# Listen for incoming connections
|
||||
sock.listen(1)
|
||||
|
||||
def send(sock, doc):
|
||||
jdoc = json.dumps(doc)
|
||||
sock.send(b'%d\n' % len(jdoc))
|
||||
sock.sendall(jdoc.encode('utf-8'))
|
||||
|
||||
def recv(sock):
|
||||
length_str = b''
|
||||
char = sock.recv(1)
|
||||
while char != b'\n':
|
||||
length_str += char
|
||||
char = sock.recv(1)
|
||||
|
||||
total = int(length_str)
|
||||
|
||||
# use a memoryview to receive the data chunk by chunk efficiently
|
||||
jdoc = memoryview(bytearray(total))
|
||||
next_offset = 0
|
||||
while total - next_offset > 0:
|
||||
recv_size = sock.recv_into(jdoc[next_offset:], total - next_offset)
|
||||
next_offset += recv_size
|
||||
try:
|
||||
doc = json.loads(jdoc.tobytes())
|
||||
except (TypeError, ValueError) as e:
|
||||
raise Exception('Data received was not in JSON format')
|
||||
return doc
|
||||
|
||||
while True:
|
||||
csock, client_address = sock.accept()
|
||||
try:
|
||||
doc = recv(csock)
|
||||
|
||||
print("%s %s" % (doc["app"], doc["argv"]), file=sys.stderr)
|
||||
oldenv = {}
|
||||
for name in doc["env"].keys():
|
||||
oldenv[name] = os.environ.get(name, None)
|
||||
os.environ[name] = doc["env"][name]
|
||||
|
||||
try:
|
||||
old_stdout = sys.stdout
|
||||
old_stderr = sys.stderr
|
||||
my_stdout = sys.stdout = StringIO()
|
||||
my_stderr = sys.stderr = StringIO()
|
||||
|
||||
class Exit(BaseException):
|
||||
def __init__(self, status):
|
||||
self.status = status
|
||||
|
||||
def noexit(stat):
|
||||
raise Exit(stat)
|
||||
|
||||
sys.exit = noexit
|
||||
|
||||
if doc["app"] == "openstack":
|
||||
sh = osc_shell.OpenStackShell()
|
||||
ret = sh.run(doc["argv"])
|
||||
else:
|
||||
print("Unknown application %s" % doc["app"], file=sys.stderr)
|
||||
ret = 1
|
||||
except Exit as e:
|
||||
ret = e.status
|
||||
finally:
|
||||
sys.stdout = old_stdout
|
||||
sys.stderr = old_stderr
|
||||
|
||||
for name in oldenv.keys():
|
||||
if oldenv[name] is None:
|
||||
del os.environ[name]
|
||||
else:
|
||||
os.environ[name] = oldenv[name]
|
||||
|
||||
send(csock, {
|
||||
"stdout": my_stdout.getvalue(),
|
||||
"stderr": my_stderr.getvalue(),
|
||||
"status": ret,
|
||||
})
|
||||
|
||||
except BaseException as e:
|
||||
print(e, file=sys.stderr)
|
||||
finally:
|
||||
csock.close()
|
@ -2438,6 +2438,11 @@ function time_stop {
|
||||
_TIME_TOTAL[$name]=$(($total + $elapsed_time))
|
||||
}
|
||||
|
||||
function install_openstack_cli_server {
|
||||
export PATH=$TOP_DIR/files/openstack-cli-server:$PATH
|
||||
run_process openstack-cli-server "$PYTHON $TOP_DIR/files/openstack-cli-server/openstack-cli-server"
|
||||
}
|
||||
|
||||
function oscwrap {
|
||||
local xtrace
|
||||
xtrace=$(set +o | grep xtrace)
|
||||
|
3
stack.sh
3
stack.sh
@ -1022,6 +1022,9 @@ if use_library_from_git "python-openstackclient"; then
|
||||
setup_dev_lib "python-openstackclient"
|
||||
else
|
||||
pip_install_gr python-openstackclient
|
||||
if is_service_enabled openstack-cli-server; then
|
||||
install_openstack_cli_server
|
||||
fi
|
||||
fi
|
||||
|
||||
# Installs alias for osc so that we can collect timing for all
|
||||
|
@ -168,6 +168,10 @@ if is_service_enabled etcd3; then
|
||||
cleanup_etcd3
|
||||
fi
|
||||
|
||||
if is_service_enabled openstack-cli-server; then
|
||||
stop_service devstack@openstack-cli-server
|
||||
fi
|
||||
|
||||
stop_dstat
|
||||
|
||||
# NOTE: Cinder automatically installs the lvm2 package, independently of the
|
||||
|
Loading…
Reference in New Issue
Block a user