Update to latest Python deploy scripts.
This commit is contained in:
Родитель
e68980d3dd
Коммит
8d359b79c0
|
@ -68,6 +68,27 @@ class ACSClient(object):
|
|||
private_key_file.seek(0)
|
||||
return paramiko.RSAKey.from_private_key(private_key_file, self.acs_info.password)
|
||||
|
||||
def ensure_dcos_version(self):
|
||||
"""
|
||||
Ensures min DC/OS version is installed on the cluster
|
||||
"""
|
||||
min_dcos_version_str = '1.8.4'
|
||||
min_dcos_version_tuple = map(int, (min_dcos_version_str.split('.')))
|
||||
path = '/dcos-metadata/dcos-version.json'
|
||||
version_json = self.get_request(path).json()
|
||||
|
||||
if not 'version' in version_json:
|
||||
raise Exception('Could not determine DC/OS version from %s', path)
|
||||
|
||||
version_str = version_json['version']
|
||||
logging.info('Found DC/OS version %s', version_str)
|
||||
version_tuple = map(int, (version_str.split('.')))
|
||||
if version_tuple < min_dcos_version_tuple:
|
||||
err_msg = 'DC/OS version %s is not supported. Only DC/OS version %s or higher is supported' % (version_str, min_dcos_version_str)
|
||||
logging.error(err_msg)
|
||||
raise ValueError(err_msg)
|
||||
return True
|
||||
|
||||
def _setup_tunnel_server(self):
|
||||
"""
|
||||
Gets the SSHTunnelForwarder instance and local_port
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
{
|
||||
"id": "/exhibitor-data",
|
||||
"cpus": 0.01,
|
||||
"mem": 32,
|
||||
"instances": 1,
|
||||
"acceptedResourceRoles": [
|
||||
"slave_public"
|
||||
],
|
||||
"container": {
|
||||
"type": "DOCKER",
|
||||
"docker": {
|
||||
"image": "openresty/openresty:alpine",
|
||||
"network": "BRIDGE",
|
||||
"portMappings": [
|
||||
{
|
||||
"protocol": "tcp",
|
||||
"hostPort": 0,
|
||||
"containerPort": 80,
|
||||
"labels": {
|
||||
"VIP_0": "exhibitor-data:80"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"cmd": "cat << EOF > /usr/local/openresty/nginx/conf/nginx.conf && exec /usr/local/openresty/bin/openresty -g 'daemon off;'\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n upstream backend {\n server leader.mesos;\n }\n server {\n listen 80;\n location = / {\n proxy_pass http://backend/exhibitor/exhibitor/v1/cluster/status;\n }\n location ~ /_/(.*)$ {\n internal;\n proxy_pass http://backend/exhibitor/exhibitor/v1/explorer/node-data?key=/\\$1;\n }\n location ~ /(.*)$ {\n content_by_lua_block {\n local cjson = require \"cjson\"\n local resp = ngx.location.capture(\"/_/\" .. ngx.var[1])\n local bytes = cjson.decode(resp.body).bytes\n bytes = string.gsub(bytes, \" \", \"\")\n if string.len(bytes) == 0 then\n ngx.status = 404\n ngx.exit(404)\n end\n for i = 1, string.len(bytes), 2 do\n ngx.print(string.char(tonumber(string.sub(bytes, i, i + 1), 16)))\n end\n }\n }\n }\n}",
|
||||
"healthChecks": [
|
||||
{
|
||||
"path": "/",
|
||||
"protocol": "HTTP",
|
||||
"portIndex": 0,
|
||||
"gracePeriodSeconds": 300,
|
||||
"intervalSeconds": 5,
|
||||
"timeoutSeconds": 20,
|
||||
"maxConsecutiveFailures": 3,
|
||||
"ignoreHttp1xx": false
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
{
|
||||
"id": "/external-nginx-lb",
|
||||
"cpus": 0.1,
|
||||
"mem": 128,
|
||||
"instances": 1,
|
||||
"acceptedResourceRoles": [
|
||||
"slave_public"
|
||||
],
|
||||
"container": {
|
||||
"type": "DOCKER",
|
||||
"docker": {
|
||||
"image": "nginx:alpine",
|
||||
"network": "HOST"
|
||||
}
|
||||
},
|
||||
"ports": [
|
||||
80,
|
||||
443
|
||||
],
|
||||
"requirePorts": true,
|
||||
"cmd": "cat << EOF > /etc/nginx/nginx.conf && exec nginx -g 'daemon off;'\nworker_processes 1;\nevents {\n worker_connections 1024;\n}\nhttp {\n server {\n listen 80;\n resolver $(echo $(cat /etc/resolv.conf | grep ^nameserver\\ | cut -d ' ' -f 2));\n location / {\n proxy_pass http://\\$host.${GROUP:-external}.marathon.l4lb.thisdcos.directory\\$request_uri;\n }\n }\n}",
|
||||
"healthChecks": [
|
||||
{
|
||||
"protocol": "TCP",
|
||||
"gracePeriodSeconds": 60,
|
||||
"intervalSeconds": 5,
|
||||
"timeoutSeconds": 2,
|
||||
"maxConsecutiveFailures": 2
|
||||
}
|
||||
],
|
||||
"upgradeStrategy": {
|
||||
"minimumHealthCapacity": 0
|
||||
}
|
||||
}
|
|
@ -108,7 +108,8 @@ if __name__ == '__main__':
|
|||
arguments.acs_port, arguments.acs_username, arguments.acs_password,
|
||||
arguments.acs_private_key, arguments.group_name, arguments.group_qualifier,
|
||||
arguments.group_version, arguments.registry_host, arguments.registry_username,
|
||||
arguments.registry_password, arguments.minimum_health_capacity) as compose_parser:
|
||||
arguments.registry_password, arguments.minimum_health_capacity,
|
||||
check_dcos_version=True) as compose_parser:
|
||||
compose_parser.deploy()
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
|
|
|
@ -11,13 +11,15 @@ import dockerregistry
|
|||
import marathon
|
||||
import portmappings
|
||||
import serviceparser
|
||||
from exhibitor import Exhibitor
|
||||
from nginx import LoadBalancerApp
|
||||
|
||||
|
||||
class DockerComposeParser(object):
|
||||
def __init__(self, compose_file, master_url, acs_host, acs_port, acs_username,
|
||||
acs_password, acs_private_key, group_name, group_qualifier, group_version,
|
||||
registry_host, registry_username, registry_password,
|
||||
minimum_health_capacity):
|
||||
minimum_health_capacity, check_dcos_version=False):
|
||||
|
||||
self._ensure_docker_compose(compose_file)
|
||||
with open(compose_file, 'r') as compose_stream:
|
||||
|
@ -37,7 +39,11 @@ class DockerComposeParser(object):
|
|||
self.minimum_health_capacity = minimum_health_capacity
|
||||
|
||||
self.acs_client = acsclient.ACSClient(self.acs_info)
|
||||
if check_dcos_version:
|
||||
self.acs_client.ensure_dcos_version()
|
||||
self.marathon_helper = marathon.Marathon(self.acs_client)
|
||||
self.exhibitor_helper = Exhibitor(self.marathon_helper)
|
||||
self.nginx_helper = LoadBalancerApp(self.marathon_helper)
|
||||
|
||||
self.portmappings_helper = portmappings.PortMappings()
|
||||
|
||||
|
@ -111,6 +117,9 @@ class DockerComposeParser(object):
|
|||
"""
|
||||
group_name = self._get_group_id()
|
||||
all_apps = {'id': group_name, 'apps': []}
|
||||
|
||||
self.nginx_helper.ensure_exists(self.compose_data)
|
||||
|
||||
docker_registry = dockerregistry.DockerRegistry(
|
||||
self.registry_host, self.registry_username, self.registry_password,
|
||||
self.marathon_helper)
|
||||
|
@ -200,6 +209,9 @@ class DockerComposeParser(object):
|
|||
except KeyError:
|
||||
raise Exception('Could not find container/docker/portMappings key')
|
||||
|
||||
if port_mappings is None:
|
||||
continue
|
||||
|
||||
# Go through port mappings and create VIPs
|
||||
for port_mapping in port_mappings:
|
||||
# Check if private IP is already created for this port mapping
|
||||
|
@ -276,6 +288,18 @@ class DockerComposeParser(object):
|
|||
self._add_host(marathon_app, app_id, private_ips)
|
||||
|
||||
def deploy(self):
|
||||
"""
|
||||
Deploys the services defined in docker-compose.yml file
|
||||
"""
|
||||
try:
|
||||
self._deploy()
|
||||
except Exception as exc:
|
||||
group_id = self._get_group_id()
|
||||
logging.exception('Exception occurred deploying "%s"', group_id)
|
||||
logging.info('Removing "%s".', group_id)
|
||||
self.marathon_helper.delete_group(group_id)
|
||||
|
||||
def _deploy(self):
|
||||
"""
|
||||
Deploys the services defined in docker-compose.yml file
|
||||
"""
|
||||
|
|
|
@ -1,24 +1,17 @@
|
|||
import json
|
||||
import logging
|
||||
|
||||
import hexifier
|
||||
import marathon
|
||||
from exhibitor import Exhibitor
|
||||
|
||||
|
||||
class DockerRegistry(object):
|
||||
"""
|
||||
Class for working with Docker registry
|
||||
"""
|
||||
EXHIBITOR_VIP = '10.1.0.0:80'
|
||||
# TODO (peterj, 10/21/2016): 281451: Update exhibitor image once ACS uses DCOS 1.8
|
||||
EXHIBITOR_DATA_IMAGE = 'mindaro/dcos-exhibitor-data:1.5.6'
|
||||
EXHIBITOR_SERVICE_ID = '/exhibitor-data'
|
||||
|
||||
def __init__(self, registry_host, registry_username, registry_password, marathon_helper):
|
||||
self.registry_host = registry_host
|
||||
self.registry_username = registry_username
|
||||
self.registry_password = registry_password
|
||||
self.marathon_helper = marathon_helper
|
||||
self.exhibitor_helper = Exhibitor(marathon_helper)
|
||||
|
||||
def get_registry_auth_url(self):
|
||||
"""
|
||||
|
@ -29,74 +22,10 @@ class DockerRegistry(object):
|
|||
if not self.registry_host:
|
||||
return None
|
||||
|
||||
self._ensure_exhibitor_service()
|
||||
self.marathon_helper.ensure_exists(Exhibitor.APP_ID, Exhibitor.JSON_FILE)
|
||||
auth_config_hexifier = hexifier.DockerAuthConfigHexifier(
|
||||
self.registry_host, self.registry_username, self.registry_password)
|
||||
|
||||
hex_string = auth_config_hexifier.hexify()
|
||||
return self._upload_auth_info(hex_string, auth_config_hexifier.get_auth_file_path())
|
||||
|
||||
def _upload_auth_info(self, hex_string, endpoint):
|
||||
"""
|
||||
Uploads the auth information (hex string) to Exhibitor
|
||||
"""
|
||||
# PUT the hex_string to exhibitor
|
||||
self.marathon_helper.put_request(
|
||||
'registries/{}'.format(endpoint),
|
||||
put_data=hex_string,
|
||||
endpoint='/exhibitor/exhibitor/v1/explorer/znode')
|
||||
|
||||
return 'http://{}/registries/{}'.format(
|
||||
self.EXHIBITOR_VIP, endpoint)
|
||||
|
||||
def _get_exhibitor_service_json(self):
|
||||
exhibitor_data_json = {
|
||||
'id': self.EXHIBITOR_SERVICE_ID,
|
||||
'cpus': 0.01,
|
||||
'mem': 32,
|
||||
'instances': 1,
|
||||
'acceptedResourceRoles': [
|
||||
'slave_public'
|
||||
],
|
||||
'container': {
|
||||
'type': 'DOCKER',
|
||||
'docker': {
|
||||
'image': self.EXHIBITOR_DATA_IMAGE,
|
||||
'network': 'BRIDGE',
|
||||
'portMappings': [
|
||||
{
|
||||
'containerPort': 80,
|
||||
'hostPort': 0,
|
||||
'protocol': 'tcp',
|
||||
'name': 'tcp80',
|
||||
'labels': {
|
||||
'VIP_0': self.EXHIBITOR_VIP
|
||||
}
|
||||
}]
|
||||
}
|
||||
},
|
||||
'healthChecks': [
|
||||
{
|
||||
'path': '/',
|
||||
'protocol': 'HTTP',
|
||||
'portIndex': 0,
|
||||
'gracePeriodSeconds': 300,
|
||||
'intervalSeconds': 5,
|
||||
'timeoutSeconds': 20,
|
||||
'maxConsecutiveFailures': 3,
|
||||
'ignoreHttp1xx': False
|
||||
}]
|
||||
}
|
||||
return exhibitor_data_json
|
||||
|
||||
def _ensure_exhibitor_service(self):
|
||||
"""
|
||||
Checks exhibitor service is running and if is not
|
||||
it will deploy it.
|
||||
"""
|
||||
exhibitor_json = self._get_exhibitor_service_json()
|
||||
# Check if app exists and deploy it if it doesn't
|
||||
app_exists = self.marathon_helper.app_exists(self.EXHIBITOR_SERVICE_ID)
|
||||
if not app_exists:
|
||||
logging.info('Exhibitor-data service not deployed - deploying it now')
|
||||
self.marathon_helper.deploy_app(json.dumps(exhibitor_json))
|
||||
endpoint = 'registries/{}'.format(auth_config_hexifier.get_auth_file_path())
|
||||
return self.exhibitor_helper.upload(hex_string, endpoint)
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
|
||||
class Exhibitor(object):
|
||||
"""
|
||||
Functionality for interacting with exhbitior
|
||||
"""
|
||||
APP_ID = '/exhibitor-data'
|
||||
HOST_NAME = 'exhibitor-data.marathon.l4lb.thisdcos.directory'
|
||||
JSON_FILE = 'conf/exhibitor-data.json'
|
||||
|
||||
def __init__(self, marathon_helper):
|
||||
self.marathon_helper = marathon_helper
|
||||
|
||||
def upload(self, hex_string, endpoint):
|
||||
"""
|
||||
Uploads a hexified string to provided exhibitor endpoint
|
||||
and returns the full URL to it
|
||||
"""
|
||||
self.marathon_helper.put_request(
|
||||
endpoint,
|
||||
put_data=hex_string,
|
||||
endpoint='/exhibitor/exhibitor/v1/explorer/znode')
|
||||
|
||||
return 'http://{}/{}'.format(
|
||||
Exhibitor.HOST_NAME, endpoint)
|
|
@ -33,15 +33,23 @@ class DockerAuthConfigHexifier(object):
|
|||
"""
|
||||
return '{}/{}'.format(self.registry_host, self._get_auth_filename())
|
||||
|
||||
@classmethod
|
||||
def hexify_file(cls, file_name):
|
||||
"""
|
||||
Creates a hex representation of a file and returns it as
|
||||
a string
|
||||
"""
|
||||
with open(file_name, 'rb') as binary_file:
|
||||
file_bytes = binary_file.read()
|
||||
hex_string = binascii.hexlify(bytearray(file_bytes))
|
||||
return hex_string
|
||||
|
||||
def hexify(self):
|
||||
"""
|
||||
Create a hex representation of the docker.tar.gz file
|
||||
"""
|
||||
file_path = self._create_temp_auth_file()
|
||||
with open(file_path, 'rb') as binary_file:
|
||||
file_bytes = binary_file.read()
|
||||
hex_string = binascii.hexlify(bytearray(file_bytes))
|
||||
return hex_string
|
||||
return DockerAuthConfigHexifier.hexify_file(file_path)
|
||||
|
||||
def _get_auth_filename(self):
|
||||
"""
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import json
|
||||
import time
|
||||
import logging
|
||||
import os
|
||||
import time
|
||||
|
||||
|
||||
class Marathon(object):
|
||||
|
@ -72,6 +73,28 @@ class Marathon(object):
|
|||
return True
|
||||
return False
|
||||
|
||||
def ensure_exists(self, app_id, json_file):
|
||||
"""
|
||||
Checks if app with provided ID is deployed on Marathon and
|
||||
deploys it if it is not
|
||||
"""
|
||||
logging.info('Check if app "%s" is deployed', app_id)
|
||||
app_exists = self.app_exists(app_id)
|
||||
if not app_exists:
|
||||
logging.info('Deploying app "%s"', app_id)
|
||||
json_contents = self._load_json(json_file)
|
||||
self.deploy_app(json.dumps(json_contents))
|
||||
|
||||
def _load_json(self, file_path):
|
||||
"""
|
||||
Loads contents of a JSON file and returns it
|
||||
"""
|
||||
file_path = os.path.join(os.getcwd(), file_path)
|
||||
with open(file_path) as json_file:
|
||||
data = json.load(json_file)
|
||||
return data
|
||||
|
||||
|
||||
def deploy_app(self, app_json):
|
||||
"""
|
||||
Deploys an app to marathon
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import os
|
||||
|
||||
from hexifier import DockerAuthConfigHexifier
|
||||
from exhibitor import Exhibitor
|
||||
|
||||
|
||||
class LoadBalancerApp(object):
|
||||
"""
|
||||
NGINX load balancer functionality
|
||||
"""
|
||||
APP_ID = '/external-nginx-lb'
|
||||
JSON_FILE = 'conf/external-nginx-lb.json'
|
||||
|
||||
def __init__(self, marathon_helper):
|
||||
self.marathon_helper = marathon_helper
|
||||
|
||||
def ensure_exists(self, compose_data):
|
||||
"""
|
||||
Checks if compose file has label that requires NGINX
|
||||
to be install and ensures it is installed
|
||||
"""
|
||||
for _, service_info in compose_data['services'].items():
|
||||
if 'labels' in service_info:
|
||||
for label in service_info['labels']:
|
||||
if label.startswith('com.microsoft.acs.dcos.marathon.vhost'):
|
||||
self.marathon_helper.ensure_exists(Exhibitor.APP_ID, Exhibitor.JSON_FILE)
|
||||
self._install()
|
||||
|
||||
def _install(self):
|
||||
"""
|
||||
Installs NGINX load balancer. Checks if NGINX is not installed yet
|
||||
then deploys the nginx.conf template first, and deploys the NGINX app.
|
||||
"""
|
||||
if not self.marathon_helper.app_exists(LoadBalancerApp.APP_ID):
|
||||
self.marathon_helper.ensure_exists(LoadBalancerApp.APP_ID, LoadBalancerApp.JSON_FILE)
|
|
@ -210,7 +210,7 @@ class PortMappings(object):
|
|||
port = str(port).strip()
|
||||
external_vip = vhost + '.external' + ':' + port
|
||||
for port_mapping in existing_port_mappings:
|
||||
if str(port_mapping['containerPort']).strip() == port:
|
||||
if str(port_mapping['containerPort']).strip() == str(port):
|
||||
port_mapping['labels']['VIP_2'] = external_vip
|
||||
vhost_added = True
|
||||
if not vhost_added:
|
||||
|
@ -221,22 +221,38 @@ class PortMappings(object):
|
|||
port_mapping['labels']['VIP_2'] = external_vip
|
||||
existing_port_mappings.append(port_mapping)
|
||||
|
||||
def _get_internal_port_mappings(self, service_data, ip_address, vip_name):
|
||||
port_mappings = []
|
||||
def _get_internal_port_mappings(self, service_data, ip_address,
|
||||
vip_name, existing_port_mappings):
|
||||
"""
|
||||
Gets the internal ports from the service data and updates the
|
||||
existing_port_mappings array
|
||||
"""
|
||||
internal_ports = self._parse_internal_ports(service_data)
|
||||
|
||||
for internal_port in internal_ports:
|
||||
port_mapping = self._get_port_mapping_json()
|
||||
vip_port = internal_port[0]
|
||||
container_port = internal_port[1]
|
||||
port_mapping['containerPort'] = int(container_port)
|
||||
port_mapping['labels']['VIP_0'] = ip_address + ':' + str(container_port)
|
||||
port_mapping['labels']['VIP_1'] = vip_name + '.internal' + ':' + str(vip_port)
|
||||
port_mappings.append(port_mapping)
|
||||
|
||||
return port_mappings
|
||||
existing_mapping = False
|
||||
for existing_port_mapping in existing_port_mappings:
|
||||
if str(existing_port_mapping['containerPort']).strip() == str(container_port):
|
||||
# No need to add VIP_0 as it already exists
|
||||
existing_port_mapping['labels']['VIP_1'] = \
|
||||
vip_name + '.internal' + ':' + str(vip_port)
|
||||
existing_mapping = True
|
||||
break
|
||||
# If we have a completely new mapping
|
||||
if not existing_mapping:
|
||||
port_mapping['containerPort'] = int(container_port)
|
||||
port_mapping['labels']['VIP_0'] = ip_address + ':' + str(container_port)
|
||||
port_mapping['labels']['VIP_1'] = vip_name + '.internal' + ':' + str(vip_port)
|
||||
existing_port_mappings.append(port_mapping)
|
||||
|
||||
def _get_private_port_mappings(self, service_data, ip_address):
|
||||
"""
|
||||
Creates a list of port mappings with private ports
|
||||
"""
|
||||
port_mappings = []
|
||||
private_ports = self._parse_private_ports(service_data)
|
||||
|
||||
|
@ -257,10 +273,8 @@ class PortMappings(object):
|
|||
split = ip_address.split(':')
|
||||
ip_address = split[0]
|
||||
|
||||
private_port_mappings = self._get_private_port_mappings(service_data, ip_address)
|
||||
internal_port_mappings = self._get_internal_port_mappings(
|
||||
service_data, ip_address, vip_name)
|
||||
all_mappings = internal_port_mappings + private_port_mappings
|
||||
|
||||
self._set_external_port_mappings(service_data, ip_address, all_mappings)
|
||||
return all_mappings
|
||||
all_port_mappings = self._get_private_port_mappings(service_data, ip_address)
|
||||
self._get_internal_port_mappings(
|
||||
service_data, ip_address, vip_name, all_port_mappings)
|
||||
self._set_external_port_mappings(service_data, ip_address, all_port_mappings)
|
||||
return all_port_mappings
|
||||
|
|
|
@ -75,7 +75,11 @@ class Parser(object):
|
|||
# If environment var does not have a value set
|
||||
self.app_json['env'][env_pair] = ''
|
||||
else:
|
||||
self.app_json['env'][env_pair] = str(self.service_info[key][env_pair])
|
||||
value = self.service_info[key][env_pair]
|
||||
if value is None:
|
||||
self.app_json['env'][env_pair] = ''
|
||||
else:
|
||||
self.app_json['env'][env_pair] = str(value)
|
||||
|
||||
def _parse_extra_hosts(self, key):
|
||||
"""
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
import unittest
|
||||
|
||||
import requests
|
||||
from mock import patch
|
||||
|
||||
import acsclient
|
||||
import acsinfo
|
||||
|
||||
|
||||
def mocked_requests_get(*args, **kwargs):
|
||||
class MockResponse:
|
||||
def __init__(self, json_data, status_code):
|
||||
self.json_data = json_data
|
||||
self.status_code = status_code
|
||||
|
||||
def json(self):
|
||||
return self.json_data
|
||||
if args[0].startswith('unsupported_version'):
|
||||
return MockResponse({'version': '1.2.3'}, 200)
|
||||
elif args[0].startswith('supported_version'):
|
||||
return MockResponse({'version': '1.8.4'}, 200)
|
||||
return MockResponse({}, 404)
|
||||
|
||||
class AcsClientTest(unittest.TestCase):
|
||||
@patch('requests.get', side_effect=mocked_requests_get)
|
||||
def test_ensure_dcos_version_unsupported(self, mock_get):
|
||||
acs_info = acsinfo.AcsInfo('myhost', 2200, None, None, None, 'unsupported_version')
|
||||
acs = acsclient.ACSClient(acs_info)
|
||||
self.assertRaises(ValueError, acs.ensure_dcos_version)
|
||||
|
||||
@patch('requests.get', side_effect=mocked_requests_get)
|
||||
def test_ensure_dcos_version_supported(self, mock_get):
|
||||
acs_info = acsinfo.AcsInfo('myhost', 2200, None, None, None, 'supported_version')
|
||||
acs = acsclient.ACSClient(acs_info)
|
||||
self.assertTrue(acs.ensure_dcos_version())
|
|
@ -158,7 +158,7 @@ class PortMappingsTest(unittest.TestCase):
|
|||
p = portmappings.PortMappings()
|
||||
expected = [{'labels': {'VIP_1': 'myvipname.internal:5000', 'VIP_0': '1.1.1.1:5000'}, 'protocol': 'tcp', 'containerPort': 5000, 'hostPort': 0}, {'labels': {'VIP_0': '1.1.1.1:3000'}, 'protocol': 'tcp', 'containerPort': 3000, 'hostPort': 0}]
|
||||
service_data = {'ports': ["5000"], 'expose': ["3000"]}
|
||||
self.assertEquals(p.get_port_mappings('1.1.1.1', service_data, 'myvipname'), expected)
|
||||
self.assertEquals(sorted(p.get_port_mappings('1.1.1.1', service_data, 'myvipname')), sorted(expected))
|
||||
|
||||
def test_get_port_mappings_single_port_range(self):
|
||||
p = portmappings.PortMappings()
|
||||
|
@ -182,7 +182,7 @@ class PortMappingsTest(unittest.TestCase):
|
|||
p = portmappings.PortMappings()
|
||||
expected = [{'labels': {'VIP_1': 'myvipname.internal:5000', 'VIP_0': '1.1.1.1:6000'}, 'protocol': 'tcp', 'containerPort': 6000, 'hostPort': 0}, {'labels': {'VIP_1': 'myvipname.internal:5001', 'VIP_0': '1.1.1.1:6001'}, 'protocol': 'tcp', 'containerPort': 6001, 'hostPort': 0}, {'labels': {'VIP_1': 'myvipname.internal:5002', 'VIP_0': '1.1.1.1:6002'}, 'protocol': 'tcp', 'containerPort': 6002, 'hostPort': 0}, {'labels': {'VIP_0': '1.1.1.1:4000'}, 'protocol': 'tcp', 'containerPort': 4000, 'hostPort': 0}]
|
||||
service_data = {'ports': ["5000-5002:6000-6002"], 'expose': ["4000"]}
|
||||
self.assertEquals(p.get_port_mappings('1.1.1.1', service_data, 'myvipname'), expected)
|
||||
self.assertEquals(sorted(p.get_port_mappings('1.1.1.1', service_data, 'myvipname')), sorted(expected))
|
||||
|
||||
def test_get_port_mappings_external_port(self):
|
||||
p = portmappings.PortMappings()
|
||||
|
|
|
@ -100,6 +100,16 @@ class ServiceParserTests(unittest.TestCase):
|
|||
p._parse_environment('environment')
|
||||
self.assertEquals({}, p.app_json)
|
||||
|
||||
def test_parse_environment_null(self):
|
||||
p = serviceparser.Parser('groupname', 'myservice', {'environment': ['SomeValue']})
|
||||
p._parse_environment('environment')
|
||||
self.assertEquals({'env': { 'SomeValue': ''}}, p.app_json)
|
||||
|
||||
def test_parse_environment_none_string(self):
|
||||
p = serviceparser.Parser('groupname', 'myservice', {'environment': ['SomeValue=None']})
|
||||
p._parse_environment('environment')
|
||||
self.assertEquals({'env': { 'SomeValue': 'None'}}, p.app_json)
|
||||
|
||||
def test_parse_extra_hosts(self):
|
||||
expected = {'container': {'docker': {'parameters': [{'key': 'add-host', 'value': 'somehost:162.242.195.82'}, {'key': 'add-host', 'value': 'otherhost:50.31.209.229'}]}}}
|
||||
p = serviceparser.Parser('groupname', 'myservice', {'extra_hosts': ['somehost:162.242.195.82', 'otherhost:50.31.209.229']})
|
||||
|
|
Загрузка…
Ссылка в новой задаче