proxy-blue print for docker swarm

People who are using magnum in VPN needs proxy
for vm instances in a bay for network. firewall
may block and user will end up in vm's without
network

Closes-Bug: #1476843
Implements: blueprint add-proxy
Co-Authored-By: Eli Qiao <liyong.qiao@intel.com>

Change-Id: I4f25918d755bd150da4f66c17ddf8b2645ab37b5
This commit is contained in:
Manjeet Singh Bhatia 2015-08-13 04:22:27 -04:00
parent 1f258903e7
commit 4ffa1d538f
15 changed files with 268 additions and 12 deletions

View File

@ -101,6 +101,16 @@ class BayModel(base.APIBase):
links = wsme.wsattr([link.Link], readonly=True)
"""A list containing a self link and associated baymodel links"""
http_proxy = wtypes.StringType(min_length=1, max_length=255)
"""http_proxy for the bay """
https_proxy = wtypes.StringType(min_length=1, max_length=255)
"""https_proxy for the bay """
no_proxy = wtypes.StringType(min_length=1, max_length=255)
"""Its comma separated list of ip for which proxies should not
used in the bay"""
def __init__(self, **kwargs):
self.fields = []
for field in objects.BayModel.fields:
@ -146,6 +156,9 @@ class BayModel(base.APIBase):
cluster_distro='fedora-atomic',
ssh_authorized_key='ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAAB',
coe='kubernetes',
http_proxy='http://proxy.com:123',
https_proxy='https://proxy.com:123',
no_proxy='192.168.0.1,192.168.0.2,192.168.0.3',
created_at=datetime.datetime.utcnow(),
updated_at=datetime.datetime.utcnow())
return cls._convert_with_links(sample, 'http://localhost:9511', expand)

View File

@ -324,6 +324,12 @@ class BaseTemplateDefinition(TemplateDefinition):
baymodel_attr='dns_nameserver')
self.add_parameter('fixed_network_cidr',
baymodel_attr='fixed_network')
self.add_parameter('http_proxy',
baymodel_attr='http_proxy')
self.add_parameter('https_proxy',
baymodel_attr='https_proxy')
self.add_parameter('no_proxy',
baymodel_attr='no_proxy')
@abc.abstractproperty
def template_path(self):
@ -446,7 +452,6 @@ class AtomicSwarmTemplateDefinition(BaseTemplateDefinition):
self.add_parameter('external_network',
baymodel_attr='external_network_id',
required=True)
self.add_output('swarm_manager',
bay_attr='api_address')
self.add_output('swarm_nodes_external',

View File

@ -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-proxy
Revision ID: 966a99e70ff
Revises: 6f21dc998bb
Create Date: 2015-08-24 11:23:24.262921
"""
# revision identifiers, used by Alembic.
revision = '966a99e70ff'
down_revision = '6f21dc998bb'
from alembic import op
import sqlalchemy as sa
def upgrade():
op.add_column('baymodel', sa.Column('http_proxy',
sa.String(length=255), nullable=True))
op.add_column('baymodel', sa.Column('https_proxy',
sa.String(length=255), nullable=True))
op.add_column('baymodel', sa.Column('no_proxy',
sa.String(length=255), nullable=True))

View File

@ -172,6 +172,9 @@ class BayModel(Base):
ssh_authorized_key = Column(Text)
cluster_distro = Column(String(255))
coe = Column(String(255))
http_proxy = Column(String(255))
https_proxy = Column(String(255))
no_proxy = Column(String(255))
class Container(Base):

View File

@ -47,6 +47,9 @@ class BayModel(base.MagnumPersistentObject, base.MagnumObject,
'ssh_authorized_key': fields.StringField(nullable=True),
'cluster_distro': fields.StringField(nullable=True),
'coe': fields.StringField(nullable=True),
'http_proxy': fields.StringField(nullable=True),
'https_proxy': fields.StringField(nullable=True),
'no_proxy': fields.StringField(nullable=True),
}
@staticmethod

View File

@ -0,0 +1,43 @@
#!/bin/sh
. /etc/sysconfig/heat-params
if [ "$HTTP_PROXY" != "" ]; then
cat > /etc/systemd/system/docker.service.d/proxy.conf <<EOF
[Service]
Environment=HTTP_PROXY=$HTTP_PROXY
EOF
systemctl daemon-reload
systemctl --no-block restart docker.service
if [ -f "/etc/bashrc" ]; then
cat >> /etc/bashrc <<EOF
declare -x http_proxy=$HTTP_PROXY
EOF
else
echo "File /etc/bashrc does not exists, not setting http_proxy"
fi
fi
if [ "$HTTPS_PROXY" != "" ]; then
if [ -f "/etc/bashrc" ]; then
cat >> /etc/bashrc <<EOF
declare -x https_proxy=$HTTPS_PROXY
EOF
else
echo "File /etc/bashrc does not exists, not setting https_proxy"
fi
fi
if [ -f "/etc/bashrc" ]; then
if [ -n "$NO_PROXY" ]; then
cat >> /etc/bashrc <<EOF
declare -x no_proxy=$NO_PROXY
EOF
else
cat>> /etc/bashrc <<EOF
declare -x no_proxy=$SWARM_MANAGER_IP,$SWARM_NODE_IP
EOF
fi
else
echo "File /etc/bashrc does not exists, not setting no_proxy"
fi

View File

@ -1,5 +1,7 @@
#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"

View File

@ -6,3 +6,9 @@ write_files:
permissions: "0644"
content: |
WAIT_HANDLE="$WAIT_HANDLE"
HTTP_PROXY="$HTTP_PROXY"
HTTPS_PROXY="$HTTPS_PROXY"
NO_PROXY="$NO_PROXY"
FIXED_NETWORK_CIDR="$FIXED_NETWORK_CIDR"
SWARM_MANAGER_IP="$SWARM_MANAGER_IP"
SWARM_NODE_IP="$NODE_IP"

View File

@ -16,7 +16,7 @@ write_files:
ExecStartPre=-/usr/bin/docker rm swarm-agent
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-agent swarm:0.2.0 join --addr $NODE_IP:2375 $DISCOVERY_URL
ExecStart=/usr/bin/docker run -e http_proxy=$HTTP_PROXY -e https_proxy=$HTTPS_PROXY -e no_proxy=$NO_PROXY --name swarm-agent swarm:0.2.0 join --addr $NODE_IP:2375 $DISCOVERY_URL
ExecStop=/usr/bin/docker stop swarm-agent
ExecStartPost=/usr/bin/curl -sf -X PUT -H 'Content-Type: application/json' \
--data-binary '{"Status": "SUCCESS", "Reason": "Setup complete", "Data": "OK", "UniqueId": "00000"}' \

View File

@ -16,7 +16,7 @@ write_files:
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 -p 2376:2375 swarm:0.2.0 manage -H tcp://0.0.0.0:2375 $DISCOVERY_URL
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"}' \

View File

@ -43,6 +43,21 @@ parameters:
description: address of a dns nameserver reachable in your environment
default: 8.8.8.8
http_proxy:
type: string
description: http proxy address for docker
default: ""
https_proxy:
type: string
description: https proxy address for docker
default: ""
no_proxy:
type: string
description: no proxies for docker
default: ""
number_of_nodes:
type: string
description: how many swarm nodes to spawn
@ -164,6 +179,10 @@ resources:
template: {get_file: fragments/write-heat-params.yaml}
params:
"$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}
"$NODE_IP": {get_attr: [swarm_manager_eth0, fixed_ips, 0, ip_address]}
remove_docker_key:
type: "OS::Heat::SoftwareConfig"
@ -200,6 +219,9 @@ resources:
"$NODE_IP": {get_attr: [swarm_manager_eth0, fixed_ips, 0, ip_address]}
"$DISCOVERY_URL": {get_param: discovery_url}
"$WAIT_HANDLE": {get_resource: agent_wait_handle}
"$HTTP_PROXY": {get_param: http_proxy}
"$HTTPS_PROXY": {get_param: https_proxy}
"$NO_PROXY": {get_param: no_proxy}
write_swarm_manager_service:
type: "OS::Heat::SoftwareConfig"
@ -211,6 +233,9 @@ resources:
params:
"$DISCOVERY_URL": {get_param: discovery_url}
"$WAIT_HANDLE": {get_resource: manager_wait_handle}
"$HTTP_PROXY": {get_param: http_proxy}
"$HTTPS_PROXY": {get_param: https_proxy}
"$NO_PROXY": {get_param: no_proxy}
enable_services:
type: "OS::Heat::SoftwareConfig"
@ -234,6 +259,12 @@ resources:
group: ungrouped
config: {get_file: fragments/disable-selinux.sh}
add_proxy:
type: "OS::Heat::SoftwareConfig"
properties:
group: ungrouped
config: {get_file: fragments/add-proxy.sh}
swarm_manager_init:
type: "OS::Heat::MultipartMime"
properties:
@ -241,6 +272,7 @@ resources:
- config: {get_resource: disable_selinux}
- config: {get_resource: remove_docker_key}
- config: {get_resource: write_heat_params}
- config: {get_resource: add_proxy}
- config: {get_resource: write_docker_service}
- config: {get_resource: write_docker_socket}
- config: {get_resource: write_docker_tcp_socket}
@ -309,6 +341,10 @@ resources:
fixed_subnet_id: {get_resource: fixed_subnet}
external_network: {get_param: external_network}
discovery_url: {get_param: discovery_url}
http_proxy: {get_param: http_proxy}
https_proxy: {get_param: https_proxy}
no_proxy: {get_param: no_proxy}
swarm_manager_ip: {get_attr: [swarm_manager_floating, floating_ip_address]}
outputs:

View File

@ -38,6 +38,25 @@ parameters:
type: string
description: url provided for node discovery
http_proxy:
type: string
description: http proxy address for docker
default: ""
https_proxy:
type: string
description: https proxy address for docker
default: ""
no_proxy:
type: string
description: no proxies for docker
default: ""
swarm_manager_ip:
type: string
description: swarm manager's ip address
resources:
node_wait_handle:
@ -96,6 +115,11 @@ resources:
template: {get_file: fragments/write-heat-params.yaml}
params:
"$WAIT_HANDLE": {get_resource: node_wait_handle}
"$HTTP_PROXY": {get_param: http_proxy}
"$HTTPS_PROXY": {get_param: https_proxy}
"$NO_PROXY": {get_param: no_proxy}
"$SWARM_MANAGER_IP": {get_param: swarm_manager_ip}
"$NODE_IP": {get_attr: [swarm_node_eth0, fixed_ips, 0, ip_address]}
remove_docker_key:
type: "OS::Heat::SoftwareConfig"
@ -155,6 +179,12 @@ resources:
group: ungrouped
config: {get_file: fragments/disable-selinux.sh}
add_proxy:
type: "OS::Heat::SoftwareConfig"
properties:
group: ungrouped
config: {get_file: fragments/add-proxy.sh}
swarm_node_init:
type: "OS::Heat::MultipartMime"
properties:
@ -162,6 +192,7 @@ resources:
- config: {get_resource: disable_selinux}
- config: {get_resource: remove_docker_key}
- config: {get_resource: write_heat_params}
- config: {get_resource: add_proxy}
- config: {get_resource: write_swarm_agent_service}
- config: {get_resource: write_docker_service}
- config: {get_resource: write_docker_socket}

View File

@ -56,6 +56,9 @@ class TestListBayModel(api_base.FunctionalTest):
self.assertNotIn('fixed_network', response['baymodels'][0])
self.assertNotIn('docker_volume_size', response['baymodels'][0])
self.assertNotIn('ssh_authorized_key', response['baymodels'][0])
self.assertNotIn('http_proxy', response['baymodels'][0])
self.assertNotIn('https_proxy', response['baymodels'][0])
self.assertNotIn('no_proxy', response['baymodels'][0])
def test_get_one(self):
baymodel = obj_utils.create_test_baymodel(self.context)
@ -70,6 +73,9 @@ class TestListBayModel(api_base.FunctionalTest):
self.assertIn('docker_volume_size', response)
self.assertIn('ssh_authorized_key', response)
self.assertIn('coe', response)
self.assertIn('http_proxy', response)
self.assertIn('https_proxy', response)
self.assertIn('no_proxy', response)
def test_get_one_by_name(self):
baymodel = obj_utils.create_test_baymodel(self.context)
@ -83,6 +89,9 @@ class TestListBayModel(api_base.FunctionalTest):
self.assertIn('fixed_network', response)
self.assertIn('docker_volume_size', response)
self.assertIn('coe', response)
self.assertIn('http_proxy', response)
self.assertIn('https_proxy', response)
self.assertIn('https_proxy', response)
def test_get_one_by_name_not_found(self):
response = self.get_json(
@ -125,7 +134,8 @@ class TestListBayModel(api_base.FunctionalTest):
self.assertEqual(baymodel.uuid, response['baymodels'][0]["uuid"])
for key in ("flavor_id", "master_flavor_id", "dns_nameserver",
"keypair_id", "external_network_id", "fixed_network",
"docker_volume_size", "ssh_authorized_key", "coe"):
"docker_volume_size", "ssh_authorized_key", "coe",
"http_proxy", "https_proxy", "no_proxy"):
self.assertIn(key, response['baymodels'][0])
def test_detail_with_pagination_marker(self):
@ -144,6 +154,18 @@ class TestListBayModel(api_base.FunctionalTest):
"keypair_id", "external_network_id", "fixed_network",
"docker_volume_size", "ssh_authorized_key", "coe"):
self.assertIn(key, response['baymodels'][0])
self.assertIn('flavor_id', response['baymodels'][0])
self.assertIn('master_flavor_id', response['baymodels'][0])
self.assertIn('dns_nameserver', response['baymodels'][0])
self.assertIn('keypair_id', response['baymodels'][0])
self.assertIn('external_network_id', response['baymodels'][0])
self.assertIn('fixed_network', response['baymodels'][0])
self.assertIn('docker_volume_size', response['baymodels'][0])
self.assertIn('ssh_authorized_key', response['baymodels'][0])
self.assertIn('coe', response['baymodels'][0])
self.assertIn('http_proxy', response['baymodels'][0])
self.assertIn('https_proxy', response['baymodels'][0])
self.assertIn('no_proxy', response['baymodels'][0])
def test_detail_against_single(self):
baymodel = obj_utils.create_test_baymodel(self.context)
@ -258,6 +280,12 @@ class TestPatch(api_base.FunctionalTest):
response['ssh_authorized_key'])
self.assertEqual(self.baymodel.coe,
response['coe'])
self.assertEqual(self.baymodel.http_proxy,
response['http_proxy'])
self.assertEqual(self.baymodel.https_proxy,
response['https_proxy'])
self.assertEqual(self.baymodel.no_proxy,
response['no_proxy'])
def test_remove_singular(self):
baymodel = obj_utils.create_test_baymodel(self.context,
@ -284,6 +312,12 @@ class TestPatch(api_base.FunctionalTest):
response['ssh_authorized_key'])
self.assertEqual(self.baymodel.coe,
response['coe'])
self.assertEqual(self.baymodel.http_proxy,
response['http_proxy'])
self.assertEqual(self.baymodel.https_proxy,
response['https_proxy'])
self.assertEqual(self.baymodel.no_proxy,
response['no_proxy'])
def test_remove_non_existent_property_fail(self):
response = self.patch_json('/baymodels/%s' % self.baymodel.uuid,
@ -354,6 +388,12 @@ class TestPatch(api_base.FunctionalTest):
response['ssh_authorized_key'])
self.assertEqual(self.baymodel.coe,
response['coe'])
self.assertEqual(self.baymodel.http_proxy,
response['http_proxy'])
self.assertEqual(self.baymodel.https_proxy,
response['https_proxy'])
self.assertEqual(self.baymodel.no_proxy,
response['no_proxy'])
def test_remove_uuid(self):
response = self.patch_json('/baymodels/%s' % self.baymodel.uuid,
@ -417,7 +457,8 @@ class TestPost(api_base.FunctionalTest):
fields = ["uuid", "name", "image_id", "flavor_id", "master_flavor_id",
"dns_nameserver", "keypair_id", "external_network_id",
"cluster_distro", "fixed_network", "apiserver_port",
"docker_volume_size"]
"docker_volume_size", "http_proxy", "https_proxy",
"no_proxy"]
for field in fields:
self._create_baymodel_raises_app_error(**{field: 'i' * 256})
@ -425,7 +466,8 @@ class TestPost(api_base.FunctionalTest):
fields = ["uuid", "name", "image_id", "flavor_id", "master_flavor_id",
"dns_nameserver", "keypair_id", "external_network_id",
"cluster_distro", "fixed_network", "apiserver_port",
"docker_volume_size", "ssh_authorized_key"]
"docker_volume_size", "ssh_authorized_key",
"http_proxy", "https_proxy", "no_proxy"]
for field in fields:
self._create_baymodel_raises_app_error(**{field: ''})

View File

@ -44,6 +44,9 @@ class TestBayConductorWithK8s(base.TestCase):
'ssh_authorized_key': 'ssh_authorized_key',
'coe': 'kubernetes',
'token': None,
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
}
self.bay_dict = {
'baymodel_id': 'xx-xx-xx-xx',
@ -88,6 +91,9 @@ class TestBayConductorWithK8s(base.TestCase):
'apiserver_port': '',
'node_count': 'number_of_minions',
'discovery_url': 'discovery_url',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
}
expected = {
'ssh_key_name': 'keypair_id',
@ -100,6 +106,9 @@ class TestBayConductorWithK8s(base.TestCase):
'fixed_network_cidr': '10.20.30.0/24',
'docker_volume_size': 20,
'discovery_url': 'https://discovery.etcd.io/test',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
}
if missing_attr is not None:
expected.pop(mapping[missing_attr], None)
@ -140,6 +149,9 @@ class TestBayConductorWithK8s(base.TestCase):
'ssh_authorized_key': 'ssh_authorized_key',
'token': 'h3',
'discovery_url': 'https://discovery.etcd.io/test',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
}
self.assertEqual(expected, definition)
@ -177,6 +189,9 @@ class TestBayConductorWithK8s(base.TestCase):
'ssh_authorized_key': 'ssh_authorized_key',
'token': 'ba3d1866282848ddbedc76112110c208',
'discovery_url': 'https://discovery.etcd.io/test',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy',
}
self.assertEqual(expected, definition)
@ -254,6 +269,9 @@ class TestBayConductorWithK8s(base.TestCase):
'fixed_network_cidr': '10.20.30.0/24',
'docker_volume_size': 20,
'discovery_url': 'https://discovery.etcd.io/test',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy'
}
self.assertIn('token', definition)
del definition['token']
@ -308,6 +326,9 @@ class TestBayConductorWithK8s(base.TestCase):
'fixed_network_cidr': '10.20.30.0/24',
'docker_volume_size': 20,
'discovery_url': 'https://address/token',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy'
}
self.assertEqual(expected, definition)
reqget.assert_called_once_with('http://etcd/test?size=1')
@ -721,7 +742,10 @@ class TestBayConductorWithSwarm(base.TestCase):
'external_network_id': 'external_network_id',
'fixed_network': '10.2.0.0/22',
'cluster_distro': 'fedora-atomic',
'coe': 'swarm'
'coe': 'swarm',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy'
}
self.bay_dict = {
'id': 1,
@ -755,7 +779,10 @@ class TestBayConductorWithSwarm(base.TestCase):
'server_flavor': 'flavor_id',
'number_of_nodes': '1',
'fixed_network_cidr': '10.2.0.0/22',
'discovery_url': 'token://39987da72f8386e0d0225ae8929e7cb4'
'discovery_url': 'token://39987da72f8386e0d0225ae8929e7cb4',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy'
}
self.assertEqual(expected, definition)
@ -768,7 +795,8 @@ class TestBayConductorWithSwarm(base.TestCase):
'test_discovery', group='bay')
not_required = ['image_id', 'flavor_id', 'dns_nameserver',
'fixed_network']
'fixed_network', 'http_proxy', 'https_proxy',
'no_proxy']
for key in not_required:
self.baymodel_dict[key] = None
self.bay_dict['discovery_url'] = None
@ -802,7 +830,10 @@ class TestBayConductorWithMesos(base.TestCase):
'external_network_id': 'external_network_id',
'fixed_network': '10.2.0.0/22',
'cluster_distro': 'ubuntu',
'coe': 'mesos'
'coe': 'mesos',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy'
}
self.bay_dict = {
'id': 1,
@ -835,7 +866,10 @@ class TestBayConductorWithMesos(base.TestCase):
'master_flavor': 'master_flavor_id',
'slave_flavor': 'flavor_id',
'number_of_slaves': '1',
'fixed_network_cidr': '10.2.0.0/22'
'fixed_network_cidr': '10.2.0.0/22',
'http_proxy': 'http_proxy',
'https_proxy': 'https_proxy',
'no_proxy': 'no_proxy'
}
self.assertEqual(expected, definition)
@ -844,7 +878,8 @@ class TestBayConductorWithMesos(base.TestCase):
self,
mock_objects_baymodel_get_by_uuid):
not_required = ['image_id', 'master_flavor_id', 'flavor_id',
'dns_nameserver', 'fixed_network']
'dns_nameserver', 'fixed_network', 'http_proxy',
'https_proxy', 'no_proxy']
for key in not_required:
self.baymodel_dict[key] = None

View File

@ -47,6 +47,9 @@ def get_test_baymodel(**kw):
'coe': kw.get('coe', 'swarm'),
'created_at': kw.get('created_at'),
'updated_at': kw.get('updated_at'),
'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'),
}