Merge "Add TLS to Docker-Swarm Template"
This commit is contained in:
commit
a9cc251e13
|
@ -0,0 +1,106 @@
|
|||
..
|
||||
Copyright 2015 Rackspace
|
||||
All Rights Reserved.
|
||||
|
||||
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.
|
||||
|
||||
========================
|
||||
Transport Layer Security
|
||||
========================
|
||||
|
||||
Magnum uses TLS to secure communication between a Bay's services and the
|
||||
outside world. This includes not only Magnum itself, but also the end-user
|
||||
when they choose to use native client libraries to interact with the Bay.
|
||||
Magnum also uses TLS certificates for client authentication, which means each
|
||||
client needs a valid certificate to communicate with a Bay.
|
||||
|
||||
TLS is a complex subject, and many guides on it exist already. This guide will
|
||||
not attempt to fully describe TLS, only the necessary pieces to get a client
|
||||
set up to talk to a Bay with TLS. A more indepth guide on TLS can be found in
|
||||
the `OpenSSL Cookbook <https://www.feistyduck.com/books/openssl-cookbook/>`_
|
||||
by Ivan Ristić.
|
||||
|
||||
|
||||
Generating a Client Key and Certificate Signing Request
|
||||
=======================================================
|
||||
|
||||
The first step to setting up a client is to generate your personal private key.
|
||||
This is essentially a cryptographically generated string of bytes. It should be
|
||||
protected as a password. To generate an RSA key, you will use the 'genrsa'
|
||||
command of the 'openssl' tool.
|
||||
|
||||
::
|
||||
|
||||
openssl genrsa -out client.key 4096
|
||||
|
||||
This command generates a 4096 byte RSA key at client.key.
|
||||
|
||||
Next, you will need to generate a certificate signing request (CSR). This will
|
||||
be used by Magnum to generate a signed certificate you will use to communicate
|
||||
with the Bay. It is used by the Bay to secure the connection and validate you
|
||||
are you who say you are.
|
||||
|
||||
To generate a CSR for client authentication, openssl requires a config file
|
||||
that specifies a few values. Below is a simple template, just fill in the 'CN'
|
||||
value with your name and save it as client.conf
|
||||
|
||||
::
|
||||
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = req_ext
|
||||
x509_extensions = req_ext
|
||||
prompt = no
|
||||
[req_distinguished_name]
|
||||
CN = Your Name
|
||||
[req_ext]
|
||||
extendedKeyUsage = clientAuth
|
||||
|
||||
Once you have client.conf, you can run the openssl 'req' command to generate
|
||||
the CSR.
|
||||
|
||||
::
|
||||
|
||||
openssl req -new -days 365
|
||||
-config client.conf
|
||||
-reqexts req_ext
|
||||
-extensions req_ext
|
||||
-key client.key
|
||||
-out client.csr
|
||||
|
||||
|
||||
Now that you have your client CSR, you can use the Magnum CLI to send it off
|
||||
to Magnum to get it signed.
|
||||
|
||||
::
|
||||
|
||||
magnum ca-sign --bay <bay-id> --csr client.csr > client.crt
|
||||
|
||||
The final piece you need to retrieve is the CA certificate for the bay. This
|
||||
is used by your native client to ensure you're only communicating with hosts
|
||||
that Magnum set up.
|
||||
|
||||
::
|
||||
|
||||
magnum ca-show --bay <bay-id> > ca.crt
|
||||
|
||||
Once you have all of these pieces, you can configure your native client. Below
|
||||
is an example for Docker.
|
||||
|
||||
::
|
||||
|
||||
docker -H tcp://<bay_api_address>:2376 --tls --tlsverify \
|
||||
--tlscacert ca.crt \
|
||||
--tlskey client.key \
|
||||
--tlscert client.crt
|
||||
info
|
|
@ -122,6 +122,9 @@ class BayModel(base.APIBase):
|
|||
labels = wtypes.DictType(str, str)
|
||||
"""One or more key/value pairs"""
|
||||
|
||||
insecure = wsme.wsattr(types.boolean, default=False)
|
||||
"""Indicates whether the TLS should be disabled"""
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.fields = []
|
||||
for field in objects.BayModel.fields:
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
# 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 uuid
|
||||
|
||||
from heatclient.common import template_utils
|
||||
from heatclient import exc
|
||||
|
@ -124,6 +125,7 @@ class Handler(object):
|
|||
try:
|
||||
# Generate certificate and set the cert reference to bay
|
||||
cert_manager.generate_certificates_to_bay(bay)
|
||||
bay.uuid = uuid.uuid4()
|
||||
created_stack = _create_stack(context, osc, bay,
|
||||
bay_create_timeout)
|
||||
except exc.HTTPBadRequest as e:
|
||||
|
|
|
@ -20,6 +20,7 @@ from pkg_resources import iter_entry_points
|
|||
import requests
|
||||
import six
|
||||
|
||||
from magnum.common import clients
|
||||
from magnum.common import exception
|
||||
from magnum.common import paths
|
||||
from magnum.i18n import _
|
||||
|
@ -107,16 +108,16 @@ class ParameterMapping(object):
|
|||
value = None
|
||||
|
||||
if (self.baymodel_attr and
|
||||
getattr(baymodel, self.baymodel_attr, None)):
|
||||
getattr(baymodel, self.baymodel_attr, None) is not None):
|
||||
value = getattr(baymodel, self.baymodel_attr)
|
||||
elif (self.bay_attr and
|
||||
getattr(bay, self.bay_attr, None)):
|
||||
getattr(bay, self.bay_attr, None) is not None):
|
||||
value = getattr(bay, self.bay_attr)
|
||||
elif self.required:
|
||||
kwargs = dict(heat_param=self.heat_param)
|
||||
raise exception.RequiredParameterNotProvided(**kwargs)
|
||||
|
||||
if value:
|
||||
if value is not None:
|
||||
value = self.param_type(value)
|
||||
params[self.heat_param] = value
|
||||
|
||||
|
@ -481,6 +482,9 @@ class AtomicSwarmTemplateDefinition(BaseTemplateDefinition):
|
|||
|
||||
def __init__(self):
|
||||
super(AtomicSwarmTemplateDefinition, self).__init__()
|
||||
self.add_parameter('bay_uuid',
|
||||
bay_attr='uuid',
|
||||
param_type=str)
|
||||
self.add_parameter('number_of_nodes',
|
||||
bay_attr='node_count',
|
||||
param_type=str)
|
||||
|
@ -489,6 +493,9 @@ class AtomicSwarmTemplateDefinition(BaseTemplateDefinition):
|
|||
self.add_parameter('external_network',
|
||||
baymodel_attr='external_network_id',
|
||||
required=True)
|
||||
self.add_parameter('insecure',
|
||||
baymodel_attr='insecure',
|
||||
required=True)
|
||||
self.add_output('swarm_master',
|
||||
bay_attr='api_address')
|
||||
self.add_output('swarm_nodes_external',
|
||||
|
@ -519,6 +526,12 @@ class AtomicSwarmTemplateDefinition(BaseTemplateDefinition):
|
|||
def get_params(self, context, baymodel, bay, **kwargs):
|
||||
extra_params = kwargs.pop('extra_params', {})
|
||||
extra_params['discovery_url'] = self.get_discovery_url(bay)
|
||||
# HACK(apmelton) - This uses the user's bearer token, ideally
|
||||
# it should be replaced with an actual trust token with only
|
||||
# access to do what the template needs it to do.
|
||||
extra_params['user_token'] = context.auth_token
|
||||
osc = clients.OpenStackClients(context)
|
||||
extra_params['magnum_url'] = osc.magnum_url()
|
||||
|
||||
return super(AtomicSwarmTemplateDefinition,
|
||||
self).get_params(context, baymodel, bay,
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
# 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.
|
||||
"""add-insecure-baymodel-attr
|
||||
|
||||
Revision ID: 1d045384b966
|
||||
Revises: 1481f5b560dd
|
||||
Create Date: 2015-09-23 18:17:10.195121
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '1d045384b966'
|
||||
down_revision = '1481f5b560dd'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
insecure_column = sa.Column('insecure', sa.Boolean(), default=False)
|
||||
op.add_column('baymodel', insecure_column)
|
||||
baymodel = sa.sql.table('baymodel', insecure_column)
|
||||
op.execute(
|
||||
baymodel.update().values({'insecure': True})
|
||||
)
|
|
@ -172,6 +172,7 @@ class BayModel(Base):
|
|||
no_proxy = Column(String(255))
|
||||
registry_enabled = Column(Boolean, default=False)
|
||||
labels = Column(JSONEncodedDict)
|
||||
insecure = Column(Boolean, default=False)
|
||||
|
||||
|
||||
class Container(Base):
|
||||
|
|
|
@ -25,7 +25,8 @@ class BayModel(base.MagnumPersistentObject, base.MagnumObject,
|
|||
# Version 1.1: Add 'registry_enabled' field
|
||||
# Version 1.2: Added 'network_driver' field
|
||||
# Version 1.3: Added 'labels' attribute
|
||||
VERSION = '1.3'
|
||||
# Version 1.4: Added 'insecure' attribute
|
||||
VERSION = '1.4'
|
||||
|
||||
dbapi = dbapi.get_instance()
|
||||
|
||||
|
@ -52,7 +53,8 @@ class BayModel(base.MagnumPersistentObject, base.MagnumObject,
|
|||
'https_proxy': fields.StringField(nullable=True),
|
||||
'no_proxy': fields.StringField(nullable=True),
|
||||
'registry_enabled': fields.BooleanField(default=False),
|
||||
'labels': fields.DictOfStringsField(nullable=True)
|
||||
'labels': fields.DictOfStringsField(nullable=True),
|
||||
'insecure': fields.BooleanField(default=False),
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
|
|
|
@ -0,0 +1,145 @@
|
|||
#!/usr/bin/python
|
||||
|
||||
# Copyright 2015 Rackspace, 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 json
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
import requests
|
||||
|
||||
HEAT_PARAMS_PATH = '/etc/sysconfig/heat-params'
|
||||
PUBLIC_IP_URL = 'http://169.254.169.254/latest/meta-data/public-ipv4'
|
||||
CERT_DIR = '/etc/docker'
|
||||
CERT_CONF_DIR = '%s/conf' % CERT_DIR
|
||||
CA_CERT_PATH = '%s/ca.crt' % CERT_DIR
|
||||
SERVER_CONF_PATH = '%s/server.conf' % CERT_CONF_DIR
|
||||
SERVER_KEY_PATH = '%s/server.key' % CERT_DIR
|
||||
SERVER_CSR_PATH = '%s/server.csr' % CERT_DIR
|
||||
SERVER_CERT_PATH = '%s/server.crt' % CERT_DIR
|
||||
|
||||
CSR_CONFIG_TEMPLATE = """
|
||||
[req]
|
||||
distinguished_name = req_distinguished_name
|
||||
req_extensions = req_ext
|
||||
x509_extensions = req_ext
|
||||
prompt = no
|
||||
copy_extensions = copyall
|
||||
[req_distinguished_name]
|
||||
CN = swarm.invalid
|
||||
[req_ext]
|
||||
subjectAltName = %(subject_alt_names)s
|
||||
extendedKeyUsage = clientAuth,serverAuth
|
||||
"""
|
||||
|
||||
|
||||
def _parse_config_value(value):
|
||||
parsed_value = value
|
||||
if parsed_value[-1] == '\n':
|
||||
parsed_value = parsed_value[:-1]
|
||||
return parsed_value[1:-1]
|
||||
|
||||
|
||||
def load_config():
|
||||
config = dict()
|
||||
with open(HEAT_PARAMS_PATH, 'r') as fp:
|
||||
for line in fp.readlines():
|
||||
key, value = line.split('=', 1)
|
||||
config[key] = _parse_config_value(value)
|
||||
return config
|
||||
|
||||
|
||||
def create_dirs():
|
||||
os.makedirs(CERT_CONF_DIR)
|
||||
|
||||
|
||||
def _get_public_ip():
|
||||
return requests.get(PUBLIC_IP_URL).text
|
||||
|
||||
|
||||
def _build_subject_alt_names(config):
|
||||
subject_alt_names = [
|
||||
'IP:%s' % _get_public_ip(),
|
||||
'IP:%s' % config['SWARM_NODE_IP'],
|
||||
'IP:127.0.0.1'
|
||||
]
|
||||
return ','.join(subject_alt_names)
|
||||
|
||||
|
||||
def write_ca_cert(config):
|
||||
bay_cert_url = '%s/certificates/%s' % (config['MAGNUM_URL'],
|
||||
config['BAY_UUID'])
|
||||
headers = {'X-Auth-Token': config['USER_TOKEN']}
|
||||
ca_cert_resp = requests.get(bay_cert_url,
|
||||
headers=headers)
|
||||
|
||||
with open(CA_CERT_PATH, 'w') as fp:
|
||||
fp.write(ca_cert_resp.json()['pem'])
|
||||
|
||||
|
||||
def write_server_key():
|
||||
subprocess.call(['openssl', 'genrsa',
|
||||
'-out', SERVER_KEY_PATH,
|
||||
'4096'])
|
||||
|
||||
|
||||
def _write_csr_config(config):
|
||||
with open(SERVER_CONF_PATH, 'w') as fp:
|
||||
params = {
|
||||
'subject_alt_names': _build_subject_alt_names(config)
|
||||
}
|
||||
fp.write(CSR_CONFIG_TEMPLATE % params)
|
||||
|
||||
|
||||
def create_server_csr(config):
|
||||
_write_csr_config(config)
|
||||
subprocess.call(['openssl', 'req', '-new',
|
||||
'-days', '1000',
|
||||
'-key', SERVER_KEY_PATH,
|
||||
'-out', SERVER_CSR_PATH,
|
||||
'-reqexts', 'req_ext',
|
||||
'-extensions', 'req_ext',
|
||||
'-config', SERVER_CONF_PATH])
|
||||
|
||||
with open(SERVER_CSR_PATH, 'r') as fp:
|
||||
return {'bay_uuid': config['BAY_UUID'], 'csr': fp.read()}
|
||||
|
||||
|
||||
def write_server_cert(config, csr_req):
|
||||
cert_url = '%s/certificates' % config['MAGNUM_URL']
|
||||
headers = {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Auth-Token': config['USER_TOKEN']
|
||||
}
|
||||
csr_resp = requests.post(cert_url,
|
||||
data=json.dumps(csr_req),
|
||||
headers=headers)
|
||||
|
||||
with open(SERVER_CERT_PATH, 'w') as fp:
|
||||
fp.write(csr_resp.json()['pem'])
|
||||
|
||||
|
||||
def main():
|
||||
config = load_config()
|
||||
if config['INSECURE'] == 'False':
|
||||
create_dirs()
|
||||
write_ca_cert(config)
|
||||
write_server_key()
|
||||
csr_req = create_server_csr(config)
|
||||
write_server_cert(config, csr_req)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
|
@ -0,0 +1,50 @@
|
|||
#!/bin/sh
|
||||
|
||||
. /etc/sysconfig/heat-params
|
||||
|
||||
mkdir -p /etc/systemd/system/docker.service.d
|
||||
|
||||
cat > /etc/systemd/system/docker.service << END_SERVICE_TOP
|
||||
[Unit]
|
||||
Description=Docker Application Container Engine
|
||||
Documentation=http://docs.docker.com
|
||||
After=network.target docker.socket
|
||||
Requires=docker.socket
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
EnvironmentFile=-/etc/sysconfig/docker
|
||||
EnvironmentFile=-/etc/sysconfig/docker-storage
|
||||
EnvironmentFile=-/etc/sysconfig/docker-network
|
||||
ExecStart=/usr/bin/docker -d -H fd:// \\
|
||||
-H tcp://0.0.0.0:2375 \\
|
||||
END_SERVICE_TOP
|
||||
|
||||
if [ $INSECURE == 'False' ]; then
|
||||
|
||||
cat >> /etc/systemd/system/docker.service << END_TLS
|
||||
--tls \\
|
||||
--tlsverify \\
|
||||
--tlscacert="/etc/docker/ca.crt" \\
|
||||
--tlskey="/etc/docker/server.key" \\
|
||||
--tlscert="/etc/docker/server.crt" \\
|
||||
END_TLS
|
||||
|
||||
fi
|
||||
|
||||
cat >> /etc/systemd/system/docker.service << END_SERVICE_BOTTOM
|
||||
\$OPTIONS \\
|
||||
\$DOCKER_STORAGE_OPTIONS \\
|
||||
\$DOCKER_NETWORK_OPTIONS \\
|
||||
\$INSECURE_REGISTRY
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=1048576
|
||||
LimitCORE=infinity
|
||||
MountFlags=slave
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
END_SERVICE_BOTTOM
|
||||
|
||||
chown root:root /etc/systemd/system/docker.service
|
||||
chmod 644 /etc/systemd/system/docker.service
|
|
@ -1,32 +0,0 @@
|
|||
#cloud-config
|
||||
merge_how: dict(recurse_array)+list(append)
|
||||
bootcmd:
|
||||
- mkdir -p /etc/systemd/system/docker.service.d
|
||||
write_files:
|
||||
- path: /etc/systemd/system/docker.service
|
||||
owner: "root:root"
|
||||
permissions: "0644"
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Docker Application Container Engine
|
||||
Documentation=http://docs.docker.com
|
||||
After=network.target docker.socket
|
||||
Requires=docker.socket
|
||||
|
||||
[Service]
|
||||
Type=notify
|
||||
EnvironmentFile=-/etc/sysconfig/docker
|
||||
EnvironmentFile=-/etc/sysconfig/docker-storage
|
||||
EnvironmentFile=-/etc/sysconfig/docker-network
|
||||
ExecStart=/usr/bin/docker -d -H fd:// \
|
||||
$OPTIONS \
|
||||
$DOCKER_STORAGE_OPTIONS \
|
||||
$DOCKER_NETWORK_OPTIONS \
|
||||
$INSECURE_REGISTRY
|
||||
LimitNOFILE=1048576
|
||||
LimitNPROC=1048576
|
||||
LimitCORE=infinity
|
||||
MountFlags=slave
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -1,18 +0,0 @@
|
|||
#cloud-config
|
||||
merge_how: dict(recurse_array)+list(append)
|
||||
write_files:
|
||||
- path: /etc/systemd/system/docker-tcp.socket
|
||||
owner: "root:root"
|
||||
permissions: "0644"
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Docker Socket for the API
|
||||
PartOf=docker.service
|
||||
|
||||
[Socket]
|
||||
ListenStream=2375
|
||||
BindIPv6Only=both
|
||||
Service=docker.service
|
||||
|
||||
[Install]
|
||||
WantedBy=sockets.target
|
|
@ -10,3 +10,8 @@ write_files:
|
|||
HTTPS_PROXY="$HTTPS_PROXY"
|
||||
NO_PROXY="$NO_PROXY"
|
||||
SWARM_MASTER_IP="$SWARM_MASTER_IP"
|
||||
SWARM_NODE_IP="$SWARM_NODE_IP"
|
||||
BAY_UUID="$BAY_UUID"
|
||||
USER_TOKEN="$USER_TOKEN"
|
||||
MAGNUM_URL="$MAGNUM_URL"
|
||||
INSECURE="$INSECURE"
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
#!/bin/sh
|
||||
|
||||
cat > /etc/systemd/system/swarm-manager.service << END_SERVICE_TOP
|
||||
[Unit]
|
||||
Description=Swarm Manager
|
||||
After=docker.service
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
TimeoutStartSec=0
|
||||
ExecStartPre=-/usr/bin/docker kill swarm-manager
|
||||
ExecStartPre=-/usr/bin/docker rm swarm-manager
|
||||
ExecStartPre=/usr/bin/docker pull swarm:0.2.0
|
||||
#TODO: roll-back from swarm:0.2.0 to swarm if atomic image can work with latest swarm image
|
||||
ExecStart=/usr/bin/docker run --name swarm-manager \\
|
||||
-v /etc/docker:/etc/docker \\
|
||||
-p 2376:2375 \\
|
||||
-e http_proxy=$HTTP_PROXY \\
|
||||
-e https_proxy=$HTTPS_PROXY \\
|
||||
-e no_proxy=$NO_PROXY \\
|
||||
swarm:0.2.0 \\
|
||||
manage -H tcp://0.0.0.0:2375 \\
|
||||
END_SERVICE_TOP
|
||||
|
||||
if [ $INSECURE = 'False' ]; then
|
||||
|
||||
cat >> /etc/systemd/system/swarm-manager.service << END_TLS
|
||||
--tls \\
|
||||
--tlsverify \\
|
||||
--tlscacert=/etc/docker/ca.crt \\
|
||||
--tlskey=/etc/docker/server.key \\
|
||||
--tlscert=/etc/docker/server.crt \\
|
||||
END_TLS
|
||||
|
||||
fi
|
||||
|
||||
cat >> /etc/systemd/system/swarm-manager.service << END_SERVICE_BOTTOM
|
||||
$DISCOVERY_URL
|
||||
ExecStop=/usr/bin/docker stop swarm-manager
|
||||
ExecStartPost=/usr/bin/curl -sf -X PUT -H 'Content-Type: application/json' \\
|
||||
--data-binary '{"Status": "SUCCESS", "Reason": "Setup complete", "Data": "OK", "UniqueId": "00000"}' \\
|
||||
"$WAIT_HANDLE"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
END_SERVICE_BOTTOM
|
||||
|
||||
chown root:root /etc/systemd/system/swarm-manager.service
|
||||
chmod 644 /etc/systemd/system/swarm-manager.service
|
|
@ -1,26 +0,0 @@
|
|||
#cloud-config
|
||||
merge_how: dict(recurse_array)+list(append)
|
||||
write_files:
|
||||
- path: /etc/systemd/system/swarm-manager.service
|
||||
owner: "root:root"
|
||||
permissions: "0644"
|
||||
content: |
|
||||
[Unit]
|
||||
Description=Swarm Manager
|
||||
After=docker.service
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
TimeoutStartSec=0
|
||||
ExecStartPre=-/usr/bin/docker kill swarm-manager
|
||||
ExecStartPre=-/usr/bin/docker rm swarm-manager
|
||||
ExecStartPre=/usr/bin/docker pull swarm:0.2.0
|
||||
#TODO: roll-back from swarm:0.2.0 to swarm if atomic image can work with latest swarm image
|
||||
ExecStart=/usr/bin/docker run -e http_proxy=$HTTP_PROXY -e https_proxy=$HTTPS_PROXY -e no_proxy=$NO_PROXY --name swarm-manager -p 2376:2375 swarm:0.2.0 manage -H tcp://0.0.0.0:2375 $DISCOVERY_URL
|
||||
ExecStop=/usr/bin/docker stop swarm-manager
|
||||
ExecStartPost=/usr/bin/curl -sf -X PUT -H 'Content-Type: application/json' \
|
||||
--data-binary '{"Status": "SUCCESS", "Reason": "Setup complete", "Data": "OK", "UniqueId": "00000"}' \
|
||||
"$WAIT_HANDLE"
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
|
@ -25,6 +25,18 @@ parameters:
|
|||
type: string
|
||||
description: url provided for node discovery
|
||||
|
||||
user_token:
|
||||
type: string
|
||||
description: token used for communicating back to Magnum for TLS certs
|
||||
|
||||
bay_uuid:
|
||||
type: string
|
||||
description: identifier for the bay this template is generating
|
||||
|
||||
magnum_url:
|
||||
type: string
|
||||
description: endpoint to retrieve TLS certs from
|
||||
|
||||
#
|
||||
# OPTIONAL PARAMETERS
|
||||
#
|
||||
|
@ -68,6 +80,11 @@ parameters:
|
|||
description: network range for fixed ip network
|
||||
default: "10.0.0.0/24"
|
||||
|
||||
insecure:
|
||||
type: boolean
|
||||
description: whether or not to enable TLS
|
||||
default: False
|
||||
|
||||
resources:
|
||||
|
||||
master_wait_handle:
|
||||
|
@ -171,6 +188,11 @@ resources:
|
|||
"$HTTPS_PROXY": {get_param: https_proxy}
|
||||
"$NO_PROXY": {get_param: no_proxy}
|
||||
"$SWARM_MASTER_IP": {get_attr: [swarm_master_eth0, fixed_ips, 0, ip_address]}
|
||||
"$SWARM_NODE_IP": {get_attr: [swarm_master_eth0, fixed_ips, 0, ip_address]}
|
||||
"$BAY_UUID": {get_param: bay_uuid}
|
||||
"$USER_TOKEN": {get_param: user_token}
|
||||
"$MAGNUM_URL": {get_param: magnum_url}
|
||||
"$INSECURE": {get_param: insecure}
|
||||
|
||||
remove_docker_key:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
|
@ -178,11 +200,17 @@ resources:
|
|||
group: ungrouped
|
||||
config: {get_file: fragments/remove-docker-key.sh}
|
||||
|
||||
make_cert:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
properties:
|
||||
group: ungrouped
|
||||
config: {get_file: fragments/make_cert.py}
|
||||
|
||||
write_docker_service:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
properties:
|
||||
group: ungrouped
|
||||
config: {get_file: fragments/write-docker-service.yaml}
|
||||
config: {get_file: fragments/write-docker-service.sh}
|
||||
|
||||
write_docker_socket:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
|
@ -190,12 +218,6 @@ resources:
|
|||
group: ungrouped
|
||||
config: {get_file: fragments/write-docker-socket.yaml}
|
||||
|
||||
write_docker_tcp_socket:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
properties:
|
||||
group: ungrouped
|
||||
config: {get_file: fragments/write-docker-tcp-socket.yaml}
|
||||
|
||||
write_swarm_agent_service:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
properties:
|
||||
|
@ -217,13 +239,14 @@ resources:
|
|||
group: ungrouped
|
||||
config:
|
||||
str_replace:
|
||||
template: {get_file: fragments/write-swarm-master-service.yaml}
|
||||
template: {get_file: fragments/write-swarm-master-service.sh}
|
||||
params:
|
||||
"$DISCOVERY_URL": {get_param: discovery_url}
|
||||
"$WAIT_HANDLE": {get_resource: master_wait_handle}
|
||||
"$HTTP_PROXY": {get_param: http_proxy}
|
||||
"$HTTPS_PROXY": {get_param: https_proxy}
|
||||
"$NO_PROXY": {get_param: no_proxy}
|
||||
"$INSECURE": {get_param: insecure}
|
||||
|
||||
enable_services:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
|
@ -261,9 +284,9 @@ resources:
|
|||
- config: {get_resource: remove_docker_key}
|
||||
- config: {get_resource: write_heat_params}
|
||||
- config: {get_resource: add_proxy}
|
||||
- config: {get_resource: make_cert}
|
||||
- config: {get_resource: write_docker_service}
|
||||
- config: {get_resource: write_docker_socket}
|
||||
- config: {get_resource: write_docker_tcp_socket}
|
||||
- config: {get_resource: write_swarm_agent_service}
|
||||
- config: {get_resource: write_swarm_master_service}
|
||||
- config: {get_resource: enable_services}
|
||||
|
@ -333,6 +356,10 @@ resources:
|
|||
https_proxy: {get_param: https_proxy}
|
||||
no_proxy: {get_param: no_proxy}
|
||||
swarm_master_ip: {get_attr: [swarm_master_eth0, fixed_ips, 0, ip_address]}
|
||||
bay_uuid: {get_param: bay_uuid}
|
||||
user_token: {get_param: user_token}
|
||||
magnum_url: {get_param: magnum_url}
|
||||
insecure: {get_param: insecure}
|
||||
|
||||
outputs:
|
||||
|
||||
|
|
|
@ -57,6 +57,22 @@ parameters:
|
|||
type: string
|
||||
description: swarm master's ip address
|
||||
|
||||
user_token:
|
||||
type: string
|
||||
description: token used for communicating back to Magnum for TLS certs
|
||||
|
||||
bay_uuid:
|
||||
type: string
|
||||
description: identifier for the bay this template is generating
|
||||
|
||||
magnum_url:
|
||||
type: string
|
||||
description: endpoint to retrieve TLS certs from
|
||||
|
||||
insecure:
|
||||
type: boolean
|
||||
description: whether or not to disable TLS
|
||||
|
||||
resources:
|
||||
|
||||
node_wait_handle:
|
||||
|
@ -119,6 +135,11 @@ resources:
|
|||
"$HTTPS_PROXY": {get_param: https_proxy}
|
||||
"$NO_PROXY": {get_param: no_proxy}
|
||||
"$SWARM_MASTER_IP": {get_param: swarm_master_ip}
|
||||
"$SWARM_NODE_IP": {get_attr: [swarm_node_eth0, fixed_ips, 0, ip_address]}
|
||||
"$BAY_UUID": {get_param: bay_uuid}
|
||||
"$USER_TOKEN": {get_param: user_token}
|
||||
"$MAGNUM_URL": {get_param: magnum_url}
|
||||
"$INSECURE": {get_param: insecure}
|
||||
|
||||
remove_docker_key:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
|
@ -126,11 +147,17 @@ resources:
|
|||
group: ungrouped
|
||||
config: {get_file: fragments/remove-docker-key.sh}
|
||||
|
||||
make_cert:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
properties:
|
||||
group: ungrouped
|
||||
config: {get_file: fragments/make_cert.py}
|
||||
|
||||
write_docker_service:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
properties:
|
||||
group: ungrouped
|
||||
config: {get_file: fragments/write-docker-service.yaml}
|
||||
config: {get_file: fragments/write-docker-service.sh}
|
||||
|
||||
write_docker_socket:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
|
@ -138,12 +165,6 @@ resources:
|
|||
group: ungrouped
|
||||
config: {get_file: fragments/write-docker-socket.yaml}
|
||||
|
||||
write_docker_tcp_socket:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
properties:
|
||||
group: ungrouped
|
||||
config: {get_file: fragments/write-docker-tcp-socket.yaml}
|
||||
|
||||
write_swarm_agent_service:
|
||||
type: "OS::Heat::SoftwareConfig"
|
||||
properties:
|
||||
|
@ -194,11 +215,11 @@ resources:
|
|||
- config: {get_resource: disable_selinux}
|
||||
- config: {get_resource: remove_docker_key}
|
||||
- config: {get_resource: write_heat_params}
|
||||
- config: {get_resource: make_cert}
|
||||
- config: {get_resource: add_proxy}
|
||||
- config: {get_resource: write_swarm_agent_service}
|
||||
- config: {get_resource: write_docker_service}
|
||||
- config: {get_resource: write_docker_socket}
|
||||
- config: {get_resource: write_docker_tcp_socket}
|
||||
- config: {get_resource: enable_services}
|
||||
- config: {get_resource: cfn_signal}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
# 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 uuid
|
||||
|
||||
from heatclient import exc
|
||||
from oslo_service import loopingcall
|
||||
|
@ -795,18 +796,51 @@ class TestHandler(db_base.DbTestCase):
|
|||
bay = objects.Bay.get(self.context, self.bay.uuid)
|
||||
self.assertEqual(bay.node_count, 1)
|
||||
|
||||
@patch('magnum.conductor.handlers.common.cert_manager.'
|
||||
'generate_certificates_to_bay')
|
||||
@patch('magnum.conductor.handlers.bay_conductor.HeatPoller')
|
||||
@patch('magnum.conductor.handlers.bay_conductor.cert_manager')
|
||||
@patch('magnum.conductor.handlers.bay_conductor._create_stack')
|
||||
@patch('magnum.conductor.handlers.bay_conductor.uuid')
|
||||
@patch('magnum.common.clients.OpenStackClients')
|
||||
def test_create(self, mock_openstack_client_class, mock_uuid,
|
||||
mock_create_stack, mock_cert_manager,
|
||||
mock_heat_poller_class):
|
||||
timeout = 15
|
||||
test_uuid = uuid.uuid4()
|
||||
mock_uuid.uuid4.return_value = test_uuid
|
||||
mock_poller = mock.MagicMock()
|
||||
mock_poller.poll_and_check.return_value = loopingcall.LoopingCallDone()
|
||||
mock_heat_poller_class.return_value = mock_poller
|
||||
mock_openstack_client_class.return_value = mock.sentinel.osc
|
||||
|
||||
def create_stack_side_effect(context, osc, bay, timeout):
|
||||
self.assertEqual(bay.uuid, str(test_uuid))
|
||||
return {'stack': {'id': 'stack-id'}}
|
||||
|
||||
mock_create_stack.side_effect = create_stack_side_effect
|
||||
|
||||
self.handler.bay_create(self.context,
|
||||
self.bay, timeout)
|
||||
|
||||
mock_create_stack.assert_called_once_with(self.context,
|
||||
mock.sentinel.osc,
|
||||
self.bay, timeout)
|
||||
mock_cert_manager.generate_certificates_to_bay.assert_called_once_with(
|
||||
self.bay)
|
||||
|
||||
@patch('magnum.conductor.handlers.bay_conductor.cert_manager')
|
||||
@patch('magnum.conductor.handlers.bay_conductor._create_stack')
|
||||
@patch('magnum.common.clients.OpenStackClients')
|
||||
def test_create(self, mock_openstack_client_class, mock_create_stack,
|
||||
mock_generate_certificates):
|
||||
def test_create_handles_bad_request(self, mock_openstack_client_class,
|
||||
mock_create_stack,
|
||||
mock_cert_manager):
|
||||
mock_create_stack.side_effect = exc.HTTPBadRequest
|
||||
timeout = 15
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
self.handler.bay_create, self.context,
|
||||
self.bay, timeout)
|
||||
mock_generate_certificates.assert_called_once_with(self.bay)
|
||||
mock_cert_manager.generate_certificates_to_bay.assert_called_once_with(
|
||||
self.bay)
|
||||
mock_cert_manager.delete_certificates_from_bay(self.bay)
|
||||
|
||||
@patch('magnum.common.clients.OpenStackClients')
|
||||
def test_bay_delete(self, mock_openstack_client_class):
|
||||
|
@ -833,7 +867,8 @@ class TestBayConductorWithSwarm(base.TestCase):
|
|||
'coe': 'swarm',
|
||||
'http_proxy': 'http_proxy',
|
||||
'https_proxy': 'https_proxy',
|
||||
'no_proxy': 'no_proxy'
|
||||
'no_proxy': 'no_proxy',
|
||||
'insecure': False
|
||||
}
|
||||
self.bay_dict = {
|
||||
'id': 1,
|
||||
|
@ -846,6 +881,12 @@ class TestBayConductorWithSwarm(base.TestCase):
|
|||
'node_count': 1,
|
||||
'discovery_url': 'token://39987da72f8386e0d0225ae8929e7cb4',
|
||||
}
|
||||
osc_patcher = mock.patch('magnum.common.clients.OpenStackClients')
|
||||
self.mock_osc_class = osc_patcher.start()
|
||||
self.addCleanup(osc_patcher.stop)
|
||||
self.mock_osc = mock.MagicMock()
|
||||
self.mock_osc.magnum_url.return_value = 'http://127.0.0.1:9511/v1'
|
||||
self.mock_osc_class.return_value = self.mock_osc
|
||||
|
||||
@patch('magnum.objects.BayModel.get_by_uuid')
|
||||
def test_extract_template_definition_all_values(
|
||||
|
@ -870,7 +911,12 @@ class TestBayConductorWithSwarm(base.TestCase):
|
|||
'discovery_url': 'token://39987da72f8386e0d0225ae8929e7cb4',
|
||||
'http_proxy': 'http_proxy',
|
||||
'https_proxy': 'https_proxy',
|
||||
'no_proxy': 'no_proxy'
|
||||
'no_proxy': 'no_proxy',
|
||||
'user_token': self.context.auth_token,
|
||||
'bay_uuid': 'some_uuid',
|
||||
'magnum_url': self.mock_osc.magnum_url.return_value,
|
||||
'insecure': False
|
||||
|
||||
}
|
||||
self.assertEqual(expected, definition)
|
||||
|
||||
|
@ -901,7 +947,11 @@ class TestBayConductorWithSwarm(base.TestCase):
|
|||
'ssh_key_name': 'keypair_id',
|
||||
'external_network': 'external_network_id',
|
||||
'number_of_nodes': '1',
|
||||
'discovery_url': 'test_discovery'
|
||||
'discovery_url': 'test_discovery',
|
||||
'user_token': self.context.auth_token,
|
||||
'bay_uuid': 'some_uuid',
|
||||
'magnum_url': self.mock_osc.magnum_url.return_value,
|
||||
'insecure': False
|
||||
}
|
||||
self.assertEqual(expected, definition)
|
||||
|
||||
|
|
|
@ -52,7 +52,8 @@ def get_test_baymodel(**kw):
|
|||
'http_proxy': kw.get('http_proxy', 'fake_http_proxy'),
|
||||
'https_proxy': kw.get('https_proxy', 'fake_https_proxy'),
|
||||
'no_proxy': kw.get('no_proxy', 'fake_no_proxy'),
|
||||
'registry_enabled': kw.get('registry_enabled', False)
|
||||
'registry_enabled': kw.get('registry_enabled', False),
|
||||
'insecure': kw.get('insecure', False)
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -426,7 +426,7 @@ class _TestObject(object):
|
|||
object_data = {
|
||||
'Bay': '1.0-35edde13ad178e9419e7ea8b6d580bcd',
|
||||
'BayLock': '1.0-7d1eb08cf2070523bd210369c7a2e076',
|
||||
'BayModel': '1.3-369d7b7f05720780ae4f6c5d983e8c3e',
|
||||
'BayModel': '1.4-68d7979ff1d81f948180fb620e6f84c7',
|
||||
'Certificate': '1.0-2aff667971b85c1edf8d15684fd7d5e2',
|
||||
'Container': '1.0-e12affbba5f8a748882a3ae98aced282',
|
||||
'MyObj': '1.0-b43567e512438205e32f4e95ca616697',
|
||||
|
|
Loading…
Reference in New Issue