Merge "Adds 'cluster' and 'cluster template'"
This commit is contained in:
commit
850f483523
@ -24,6 +24,7 @@ import os
|
|||||||
import sys
|
import sys
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
|
import decorator
|
||||||
from magnumclient.common.apiclient import exceptions
|
from magnumclient.common.apiclient import exceptions
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
from oslo_utils import strutils
|
from oslo_utils import strutils
|
||||||
@ -75,6 +76,22 @@ def validate_args(fn, *args, **kwargs):
|
|||||||
raise MissingArgs(missing)
|
raise MissingArgs(missing)
|
||||||
|
|
||||||
|
|
||||||
|
def deprecated(message):
|
||||||
|
'''Decorator for marking a call as deprecated by printing a given message.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
>>> @deprecated("Bay functions are deprecated and should be replaced by "
|
||||||
|
... "calls to cluster")
|
||||||
|
... def bay_create(args):
|
||||||
|
... pass
|
||||||
|
'''
|
||||||
|
@decorator.decorator
|
||||||
|
def wrapper(func, *args, **kwargs):
|
||||||
|
print(message)
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
def arg(*args, **kwargs):
|
def arg(*args, **kwargs):
|
||||||
"""Decorator for CLI args.
|
"""Decorator for CLI args.
|
||||||
|
|
||||||
|
304
magnumclient/tests/v1/test_clusters.py
Normal file
304
magnumclient/tests/v1/test_clusters.py
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
# Copyright 2015 IBM Corp.
|
||||||
|
#
|
||||||
|
# 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 copy
|
||||||
|
|
||||||
|
import testtools
|
||||||
|
from testtools import matchers
|
||||||
|
|
||||||
|
from magnumclient import exceptions
|
||||||
|
from magnumclient.tests import utils
|
||||||
|
from magnumclient.v1 import clusters
|
||||||
|
|
||||||
|
|
||||||
|
CLUSTER1 = {'id': 123,
|
||||||
|
'uuid': '66666666-7777-8888-9999-000000000001',
|
||||||
|
'name': 'cluster1',
|
||||||
|
'cluster_template_id': 'e74c40e0-d825-11e2-a28f-0800200c9a61',
|
||||||
|
'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a51',
|
||||||
|
'api_address': '172.17.2.1',
|
||||||
|
'node_addresses': ['172.17.2.3'],
|
||||||
|
'node_count': 2,
|
||||||
|
'master_count': 1,
|
||||||
|
}
|
||||||
|
CLUSTER2 = {'id': 124,
|
||||||
|
'uuid': '66666666-7777-8888-9999-000000000002',
|
||||||
|
'name': 'cluster2',
|
||||||
|
'cluster_template_id': 'e74c40e0-d825-11e2-a28f-0800200c9a62',
|
||||||
|
'stack_id': '5d12f6fd-a196-4bf0-ae4c-1f639a523a52',
|
||||||
|
'api_address': '172.17.2.2',
|
||||||
|
'node_addresses': ['172.17.2.4'],
|
||||||
|
'node_count': 2,
|
||||||
|
'master_count': 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
CREATE_CLUSTER = copy.deepcopy(CLUSTER1)
|
||||||
|
del CREATE_CLUSTER['id']
|
||||||
|
del CREATE_CLUSTER['uuid']
|
||||||
|
del CREATE_CLUSTER['stack_id']
|
||||||
|
del CREATE_CLUSTER['api_address']
|
||||||
|
del CREATE_CLUSTER['node_addresses']
|
||||||
|
|
||||||
|
UPDATED_CLUSTER = copy.deepcopy(CLUSTER1)
|
||||||
|
NEW_NAME = 'newcluster'
|
||||||
|
UPDATED_CLUSTER['name'] = NEW_NAME
|
||||||
|
|
||||||
|
fake_responses = {
|
||||||
|
'/v1/clusters':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||||
|
),
|
||||||
|
'POST': (
|
||||||
|
{},
|
||||||
|
CREATE_CLUSTER,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clusters/%s' % CLUSTER1['id']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
CLUSTER1
|
||||||
|
),
|
||||||
|
'DELETE': (
|
||||||
|
{},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
'PATCH': (
|
||||||
|
{},
|
||||||
|
UPDATED_CLUSTER,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clusters/%s' % CLUSTER1['name']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
CLUSTER1
|
||||||
|
),
|
||||||
|
'DELETE': (
|
||||||
|
{},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
'PATCH': (
|
||||||
|
{},
|
||||||
|
UPDATED_CLUSTER,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clusters/?limit=2':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clusters/?marker=%s' % CLUSTER2['uuid']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clusters/?limit=2&marker=%s' % CLUSTER2['uuid']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clusters/?sort_dir=asc':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clusters/?sort_key=uuid':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clusters': [CLUSTER1, CLUSTER2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clusters/?sort_key=uuid&sort_dir=desc':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clusters': [CLUSTER2, CLUSTER1]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterManagerTest(testtools.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ClusterManagerTest, self).setUp()
|
||||||
|
self.api = utils.FakeAPI(fake_responses)
|
||||||
|
self.mgr = clusters.ClusterManager(self.api)
|
||||||
|
|
||||||
|
def test_cluster_list(self):
|
||||||
|
clusters = self.mgr.list()
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clusters', {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertThat(clusters, matchers.HasLength(2))
|
||||||
|
|
||||||
|
def _test_cluster_list_with_filters(self, limit=None, marker=None,
|
||||||
|
sort_key=None, sort_dir=None,
|
||||||
|
detail=False, expect=[]):
|
||||||
|
clusters_filter = self.mgr.list(limit=limit,
|
||||||
|
marker=marker,
|
||||||
|
sort_key=sort_key,
|
||||||
|
sort_dir=sort_dir,
|
||||||
|
detail=detail)
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertThat(clusters_filter, matchers.HasLength(2))
|
||||||
|
|
||||||
|
def test_cluster_list_with_limit(self):
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clusters/?limit=2', {}, None),
|
||||||
|
]
|
||||||
|
self._test_cluster_list_with_filters(
|
||||||
|
limit=2,
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_cluster_list_with_marker(self):
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clusters/?marker=%s' % CLUSTER2['uuid'], {}, None),
|
||||||
|
]
|
||||||
|
self._test_cluster_list_with_filters(
|
||||||
|
marker=CLUSTER2['uuid'],
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_cluster_list_with_marker_limit(self):
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clusters/?limit=2&marker=%s' % CLUSTER2['uuid'],
|
||||||
|
{},
|
||||||
|
None),
|
||||||
|
]
|
||||||
|
self._test_cluster_list_with_filters(
|
||||||
|
limit=2, marker=CLUSTER2['uuid'],
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_cluster_list_with_sort_dir(self):
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clusters/?sort_dir=asc', {}, None),
|
||||||
|
]
|
||||||
|
self._test_cluster_list_with_filters(
|
||||||
|
sort_dir='asc',
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_cluster_list_with_sort_key(self):
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clusters/?sort_key=uuid', {}, None),
|
||||||
|
]
|
||||||
|
self._test_cluster_list_with_filters(
|
||||||
|
sort_key='uuid',
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_cluster_list_with_sort_key_dir(self):
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clusters/?sort_key=uuid&sort_dir=desc', {}, None),
|
||||||
|
]
|
||||||
|
self._test_cluster_list_with_filters(
|
||||||
|
sort_key='uuid', sort_dir='desc',
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_cluster_show_by_id(self):
|
||||||
|
cluster = self.mgr.get(CLUSTER1['id'])
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clusters/%s' % CLUSTER1['id'], {}, None)
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(CLUSTER1['name'], cluster.name)
|
||||||
|
self.assertEqual(CLUSTER1['cluster_template_id'],
|
||||||
|
cluster.cluster_template_id)
|
||||||
|
|
||||||
|
def test_cluster_show_by_name(self):
|
||||||
|
cluster = self.mgr.get(CLUSTER1['name'])
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clusters/%s' % CLUSTER1['name'], {}, None)
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(CLUSTER1['name'], cluster.name)
|
||||||
|
self.assertEqual(CLUSTER1['cluster_template_id'],
|
||||||
|
cluster.cluster_template_id)
|
||||||
|
|
||||||
|
def test_cluster_create(self):
|
||||||
|
cluster = self.mgr.create(**CREATE_CLUSTER)
|
||||||
|
expect = [
|
||||||
|
('POST', '/v1/clusters', {}, CREATE_CLUSTER),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertTrue(cluster)
|
||||||
|
|
||||||
|
def test_cluster_create_with_discovery_url(self):
|
||||||
|
cluster_with_discovery = dict()
|
||||||
|
cluster_with_discovery.update(CREATE_CLUSTER)
|
||||||
|
cluster_with_discovery['discovery_url'] = 'discovery_url'
|
||||||
|
cluster = self.mgr.create(**cluster_with_discovery)
|
||||||
|
expect = [
|
||||||
|
('POST', '/v1/clusters', {}, cluster_with_discovery),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertTrue(cluster)
|
||||||
|
|
||||||
|
def test_cluster_create_with_cluster_create_timeout(self):
|
||||||
|
cluster_with_timeout = dict()
|
||||||
|
cluster_with_timeout.update(CREATE_CLUSTER)
|
||||||
|
cluster_with_timeout['create_timeout'] = '15'
|
||||||
|
cluster = self.mgr.create(**cluster_with_timeout)
|
||||||
|
expect = [
|
||||||
|
('POST', '/v1/clusters', {}, cluster_with_timeout),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertTrue(cluster)
|
||||||
|
|
||||||
|
def test_cluster_create_fail(self):
|
||||||
|
CREATE_CLUSTER_FAIL = copy.deepcopy(CREATE_CLUSTER)
|
||||||
|
CREATE_CLUSTER_FAIL["wrong_key"] = "wrong"
|
||||||
|
self.assertRaisesRegexp(exceptions.InvalidAttribute,
|
||||||
|
("Key must be in %s" %
|
||||||
|
','.join(clusters.CREATION_ATTRIBUTES)),
|
||||||
|
self.mgr.create, **CREATE_CLUSTER_FAIL)
|
||||||
|
self.assertEqual([], self.api.calls)
|
||||||
|
|
||||||
|
def test_cluster_delete_by_id(self):
|
||||||
|
cluster = self.mgr.delete(CLUSTER1['id'])
|
||||||
|
expect = [
|
||||||
|
('DELETE', '/v1/clusters/%s' % CLUSTER1['id'], {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertIsNone(cluster)
|
||||||
|
|
||||||
|
def test_cluster_delete_by_name(self):
|
||||||
|
cluster = self.mgr.delete(CLUSTER1['name'])
|
||||||
|
expect = [
|
||||||
|
('DELETE', '/v1/clusters/%s' % CLUSTER1['name'], {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertIsNone(cluster)
|
||||||
|
|
||||||
|
def test_cluster_update(self):
|
||||||
|
patch = {'op': 'replace',
|
||||||
|
'value': NEW_NAME,
|
||||||
|
'path': '/name'}
|
||||||
|
cluster = self.mgr.update(id=CLUSTER1['id'], patch=patch)
|
||||||
|
expect = [
|
||||||
|
('PATCH', '/v1/clusters/%s' % CLUSTER1['id'], {}, patch),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(NEW_NAME, cluster.name)
|
298
magnumclient/tests/v1/test_clusters_shell.py
Normal file
298
magnumclient/tests/v1/test_clusters_shell.py
Normal file
@ -0,0 +1,298 @@
|
|||||||
|
# Copyright 2015 NEC Corporation. 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.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from magnumclient import exceptions
|
||||||
|
from magnumclient.tests.v1 import shell_test_base
|
||||||
|
from magnumclient.tests.v1 import test_clustertemplates_shell
|
||||||
|
from magnumclient.v1.clusters import Cluster
|
||||||
|
|
||||||
|
|
||||||
|
class FakeCluster(Cluster):
|
||||||
|
def __init__(self, manager=None, info={}, **kwargs):
|
||||||
|
Cluster.__init__(self, manager=manager, info=info)
|
||||||
|
self.uuid = kwargs.get('uuid', 'x')
|
||||||
|
self.name = kwargs.get('name', 'x')
|
||||||
|
self.cluster_template_id = kwargs.get('cluster_template_id', 'x')
|
||||||
|
self.stack_id = kwargs.get('stack_id', 'x')
|
||||||
|
self.status = kwargs.get('status', 'x')
|
||||||
|
self.master_count = kwargs.get('master_count', 1)
|
||||||
|
self.node_count = kwargs.get('node_count', 1)
|
||||||
|
self.links = kwargs.get('links', [])
|
||||||
|
self.create_timeout = kwargs.get('create_timeout', 60)
|
||||||
|
|
||||||
|
|
||||||
|
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||||
|
def test_cluster_list_success(self, mock_list):
|
||||||
|
self._test_arg_success('cluster-list')
|
||||||
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||||
|
def test_cluster_list_success_with_arg(self, mock_list):
|
||||||
|
self._test_arg_success('cluster-list '
|
||||||
|
'--marker some_uuid '
|
||||||
|
'--limit 1 '
|
||||||
|
'--sort-dir asc '
|
||||||
|
'--sort-key uuid')
|
||||||
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||||
|
def test_cluster_list_ignored_duplicated_field(self, mock_list):
|
||||||
|
mock_list.return_value = [FakeCluster()]
|
||||||
|
self._test_arg_success(
|
||||||
|
'cluster-list --fields status,status,status,name',
|
||||||
|
keyword=('\n| uuid | name | node_count | '
|
||||||
|
'master_count | status |\n'))
|
||||||
|
# Output should be
|
||||||
|
# +------+------+------------+--------------+--------+
|
||||||
|
# | uuid | name | node_count | master_count | status |
|
||||||
|
# +------+------+------------+--------------+--------+
|
||||||
|
# | x | x | x | x | x |
|
||||||
|
# +------+------+------------+--------------+--------+
|
||||||
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||||
|
def test_cluster_list_failure_with_invalid_field(self, mock_list):
|
||||||
|
mock_list.return_value = [FakeCluster()]
|
||||||
|
_error_msg = [".*?^Non-existent fields are specified: ['xxx','zzz']"]
|
||||||
|
self.assertRaises(exceptions.CommandError,
|
||||||
|
self._test_arg_failure,
|
||||||
|
'cluster-list --fields xxx,stack_id,zzz,status',
|
||||||
|
_error_msg)
|
||||||
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||||
|
def test_cluster_list_failure_invalid_arg(self, mock_list):
|
||||||
|
_error_msg = [
|
||||||
|
'.*?^usage: magnum cluster-list ',
|
||||||
|
'.*?^error: argument --sort-dir: invalid choice: ',
|
||||||
|
".*?^Try 'magnum help cluster-list' for more information."
|
||||||
|
]
|
||||||
|
self._test_arg_failure('cluster-list --sort-dir aaa', _error_msg)
|
||||||
|
self.assertFalse(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.list')
|
||||||
|
def test_cluster_list_failure(self, mock_list):
|
||||||
|
self._test_arg_failure('cluster-list --wrong',
|
||||||
|
self._unrecognized_arg_error)
|
||||||
|
self.assertFalse(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||||
|
def test_cluster_create_success(self, mock_create, mock_get):
|
||||||
|
self._test_arg_success('cluster-create --name test '
|
||||||
|
'--cluster-template xxx '
|
||||||
|
'--node-count 123 --timeout 15')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-create --cluster-template xxx')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-create --name test '
|
||||||
|
'--cluster-template xxx')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-create --cluster-template xxx '
|
||||||
|
'--node-count 123')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-create --cluster-template xxx '
|
||||||
|
'--node-count 123 --master-count 123')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-create --cluster-template xxx '
|
||||||
|
'--timeout 15')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||||
|
def test_cluster_show_clustertemplate_metadata(self,
|
||||||
|
mock_cluster,
|
||||||
|
mock_clustertemplate):
|
||||||
|
mock_cluster.return_value = FakeCluster(info={'links': 0,
|
||||||
|
'baymodel_id': 0})
|
||||||
|
mock_clustertemplate.return_value = \
|
||||||
|
test_clustertemplates_shell.FakeClusterTemplate(info={'links': 0,
|
||||||
|
'uuid': 0,
|
||||||
|
'id': 0,
|
||||||
|
'name': ''})
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-show --long x', 'clustertemplate_name')
|
||||||
|
self.assertTrue(mock_cluster.called)
|
||||||
|
self.assertTrue(mock_clustertemplate.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||||
|
def test_cluster_create_success_only_clustertemplate_arg(self,
|
||||||
|
mock_create,
|
||||||
|
mock_get):
|
||||||
|
self._test_arg_success('cluster-create --cluster-template xxx')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||||
|
def test_cluster_create_failure_only_name(self, mock_create):
|
||||||
|
self._test_arg_failure('cluster-create --name test',
|
||||||
|
self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||||
|
def test_cluster_create_failure_only_node_count(self, mock_create):
|
||||||
|
self._test_arg_failure('cluster-create --node-count 1',
|
||||||
|
self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||||
|
def test_cluster_create_failure_invalid_node_count(self, mock_create):
|
||||||
|
self._test_arg_failure('cluster-create --cluster-template xxx '
|
||||||
|
'--node-count test',
|
||||||
|
self._invalid_value_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||||
|
def test_cluster_create_failure_only_cluster_create_timeout(self,
|
||||||
|
mock_create):
|
||||||
|
self._test_arg_failure('cluster-create --timeout 15',
|
||||||
|
self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||||
|
def test_cluster_create_failure_no_arg(self, mock_create):
|
||||||
|
self._test_arg_failure('cluster-create',
|
||||||
|
self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.create')
|
||||||
|
def test_cluster_create_failure_invalid_master_count(self, mock_create):
|
||||||
|
self._test_arg_failure('cluster-create --cluster-template xxx '
|
||||||
|
'--master-count test',
|
||||||
|
self._invalid_value_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.delete')
|
||||||
|
def test_cluster_delete_success(self, mock_delete):
|
||||||
|
self._test_arg_success('cluster-delete xxx')
|
||||||
|
self.assertTrue(mock_delete.called)
|
||||||
|
self.assertEqual(1, mock_delete.call_count)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.delete')
|
||||||
|
def test_cluster_delete_multiple_id_success(self, mock_delete):
|
||||||
|
self._test_arg_success('cluster-delete xxx xyz')
|
||||||
|
self.assertTrue(mock_delete.called)
|
||||||
|
self.assertEqual(2, mock_delete.call_count)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.delete')
|
||||||
|
def test_cluster_delete_failure_no_arg(self, mock_delete):
|
||||||
|
self._test_arg_failure('cluster-delete', self._few_argument_error)
|
||||||
|
self.assertFalse(mock_delete.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||||
|
def test_cluster_show_success(self, mock_show):
|
||||||
|
self._test_arg_success('cluster-show xxx')
|
||||||
|
self.assertTrue(mock_show.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||||
|
def test_cluster_show_failure_no_arg(self, mock_show):
|
||||||
|
self._test_arg_failure('cluster-show', self._few_argument_error)
|
||||||
|
self.assertFalse(mock_show.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||||
|
def test_cluster_update_success(self, mock_update):
|
||||||
|
self._test_arg_success('cluster-update test add test=test')
|
||||||
|
self.assertTrue(mock_update.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||||
|
def test_cluster_update_success_many_attribute(self, mock_update):
|
||||||
|
self._test_arg_success('cluster-update test add test=test test1=test1')
|
||||||
|
self.assertTrue(mock_update.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||||
|
def test_cluster_update_failure_wrong_op(self, mock_update):
|
||||||
|
_error_msg = [
|
||||||
|
'.*?^usage: magnum cluster-update ',
|
||||||
|
'.*?^error: argument <op>: invalid choice: ',
|
||||||
|
".*?^Try 'magnum help cluster-update' for more information."
|
||||||
|
]
|
||||||
|
self._test_arg_failure('cluster-update test wrong test=test',
|
||||||
|
_error_msg)
|
||||||
|
self.assertFalse(mock_update.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||||
|
def test_cluster_update_failure_wrong_attribute(self, mock_update):
|
||||||
|
_error_msg = [
|
||||||
|
'.*?^ERROR: Attributes must be a list of PATH=VALUE'
|
||||||
|
]
|
||||||
|
self.assertRaises(exceptions.CommandError, self._test_arg_failure,
|
||||||
|
'cluster-update test add test', _error_msg)
|
||||||
|
self.assertFalse(mock_update.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.update')
|
||||||
|
def test_cluster_update_failure_few_args(self, mock_update):
|
||||||
|
_error_msg = [
|
||||||
|
'.*?^usage: magnum cluster-update ',
|
||||||
|
'.*?^error: (the following arguments|too few arguments)',
|
||||||
|
".*?^Try 'magnum help cluster-update' for more information."
|
||||||
|
]
|
||||||
|
self._test_arg_failure('cluster-update', _error_msg)
|
||||||
|
self.assertFalse(mock_update.called)
|
||||||
|
|
||||||
|
self._test_arg_failure('cluster-update test', _error_msg)
|
||||||
|
self.assertFalse(mock_update.called)
|
||||||
|
|
||||||
|
self._test_arg_failure('cluster-update test add', _error_msg)
|
||||||
|
self.assertFalse(mock_update.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||||
|
def test_cluster_config_success(self, mock_cluster, mock_clustertemplate):
|
||||||
|
mock_cluster.return_value = FakeCluster(status='UPDATE_COMPLETE')
|
||||||
|
self._test_arg_success('cluster-config xxx')
|
||||||
|
self.assertTrue(mock_cluster.called)
|
||||||
|
|
||||||
|
mock_cluster.return_value = FakeCluster(status='CREATE_COMPLETE')
|
||||||
|
self._test_arg_success('cluster-config xxx')
|
||||||
|
self.assertTrue(mock_cluster.called)
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-config --dir /tmp xxx')
|
||||||
|
self.assertTrue(mock_cluster.called)
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-config --force xxx')
|
||||||
|
self.assertTrue(mock_cluster.called)
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-config --dir /tmp --force xxx')
|
||||||
|
self.assertTrue(mock_cluster.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||||
|
def test_cluster_config_failure_wrong_status(self,
|
||||||
|
mock_cluster,
|
||||||
|
mock_clustertemplate):
|
||||||
|
mock_cluster.return_value = FakeCluster(status='CREATE_IN_PROGRESS')
|
||||||
|
self.assertRaises(exceptions.CommandError,
|
||||||
|
self._test_arg_failure,
|
||||||
|
'cluster-config xxx',
|
||||||
|
['.*?^Cluster in status: '])
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||||
|
def test_cluster_config_failure_no_arg(self, mock_cluster):
|
||||||
|
self._test_arg_failure('cluster-config', self._few_argument_error)
|
||||||
|
self.assertFalse(mock_cluster.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.clusters.ClusterManager.get')
|
||||||
|
def test_cluster_config_failure_wrong_arg(self, mock_cluster):
|
||||||
|
self._test_arg_failure('cluster-config xxx yyy',
|
||||||
|
self._unrecognized_arg_error)
|
||||||
|
self.assertFalse(mock_cluster.called)
|
412
magnumclient/tests/v1/test_clustertemplates.py
Normal file
412
magnumclient/tests/v1/test_clustertemplates.py
Normal file
@ -0,0 +1,412 @@
|
|||||||
|
# Copyright 2015 IBM Corp.
|
||||||
|
#
|
||||||
|
# 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 copy
|
||||||
|
|
||||||
|
import testtools
|
||||||
|
from testtools import matchers
|
||||||
|
|
||||||
|
from magnumclient import exceptions
|
||||||
|
from magnumclient.tests import utils
|
||||||
|
from magnumclient.v1 import cluster_templates
|
||||||
|
|
||||||
|
|
||||||
|
CLUSTERTEMPLATE1 = {
|
||||||
|
'id': 123,
|
||||||
|
'uuid': '66666666-7777-8888-9999-000000000001',
|
||||||
|
'name': 'clustertemplate1',
|
||||||
|
'image_id': 'clustertemplate1-image',
|
||||||
|
'master_flavor_id': 'm1.tiny',
|
||||||
|
'flavor_id': 'm1.small',
|
||||||
|
'keypair_id': 'keypair1',
|
||||||
|
'external_network_id': 'd1f02cfb-d27f-4068-9332-84d907cb0e21',
|
||||||
|
'fixed_network': 'private',
|
||||||
|
'fixed_subnet': 'private-subnet',
|
||||||
|
'network_driver': 'libnetwork',
|
||||||
|
'volume_driver': 'rexray',
|
||||||
|
'dns_nameserver': '8.8.1.1',
|
||||||
|
'docker_volume_size': '71',
|
||||||
|
'docker_storage_driver': 'devicemapper',
|
||||||
|
'coe': 'swarm',
|
||||||
|
'http_proxy': 'http_proxy',
|
||||||
|
'https_proxy': 'https_proxy',
|
||||||
|
'no_proxy': 'no_proxy',
|
||||||
|
'labels': 'key1=val1,key11=val11',
|
||||||
|
'tls_disabled': False,
|
||||||
|
'public': False,
|
||||||
|
'registry_enabled': False,
|
||||||
|
'master_lb_enabled': True}
|
||||||
|
|
||||||
|
CLUSTERTEMPLATE2 = {
|
||||||
|
'id': 124,
|
||||||
|
'uuid': '66666666-7777-8888-9999-000000000002',
|
||||||
|
'name': 'clustertemplate2',
|
||||||
|
'image_id': 'clustertemplate2-image',
|
||||||
|
'flavor_id': 'm2.small',
|
||||||
|
'master_flavor_id': 'm2.tiny',
|
||||||
|
'keypair_id': 'keypair2',
|
||||||
|
'external_network_id': 'd1f02cfb-d27f-4068-9332-84d907cb0e22',
|
||||||
|
'fixed_network': 'private2',
|
||||||
|
'network_driver': 'flannel',
|
||||||
|
'volume_driver': 'cinder',
|
||||||
|
'dns_nameserver': '8.8.1.2',
|
||||||
|
'docker_volume_size': '50',
|
||||||
|
'docker_storage_driver': 'overlay',
|
||||||
|
'coe': 'kubernetes',
|
||||||
|
'labels': 'key2=val2,key22=val22',
|
||||||
|
'tls_disabled': True,
|
||||||
|
'public': True,
|
||||||
|
'registry_enabled': True}
|
||||||
|
|
||||||
|
CREATE_CLUSTERTEMPLATE = copy.deepcopy(CLUSTERTEMPLATE1)
|
||||||
|
del CREATE_CLUSTERTEMPLATE['id']
|
||||||
|
del CREATE_CLUSTERTEMPLATE['uuid']
|
||||||
|
|
||||||
|
UPDATED_CLUSTERTEMPLATE = copy.deepcopy(CLUSTERTEMPLATE1)
|
||||||
|
NEW_NAME = 'newcluster'
|
||||||
|
UPDATED_CLUSTERTEMPLATE['name'] = NEW_NAME
|
||||||
|
|
||||||
|
fake_responses = {
|
||||||
|
'/v1/clustertemplates':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||||
|
),
|
||||||
|
'POST': (
|
||||||
|
{},
|
||||||
|
CREATE_CLUSTERTEMPLATE,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
CLUSTERTEMPLATE1
|
||||||
|
),
|
||||||
|
'DELETE': (
|
||||||
|
{},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
'PATCH': (
|
||||||
|
{},
|
||||||
|
UPDATED_CLUSTERTEMPLATE,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['name']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
CLUSTERTEMPLATE1
|
||||||
|
),
|
||||||
|
'DELETE': (
|
||||||
|
{},
|
||||||
|
None,
|
||||||
|
),
|
||||||
|
'PATCH': (
|
||||||
|
{},
|
||||||
|
UPDATED_CLUSTERTEMPLATE,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clustertemplates/detail':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clustertemplates/?limit=2':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clustertemplates/?marker=%s' % CLUSTERTEMPLATE2['uuid']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clustertemplates/?limit=2&marker=%s' % CLUSTERTEMPLATE2['uuid']:
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clustertemplates/?sort_dir=asc':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clustertemplates/?sort_key=uuid':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clustertemplates': [CLUSTERTEMPLATE1, CLUSTERTEMPLATE2]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
'/v1/clustertemplates/?sort_key=uuid&sort_dir=desc':
|
||||||
|
{
|
||||||
|
'GET': (
|
||||||
|
{},
|
||||||
|
{'clustertemplates': [CLUSTERTEMPLATE2, CLUSTERTEMPLATE1]},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterTemplateManagerTest(testtools.TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(ClusterTemplateManagerTest, self).setUp()
|
||||||
|
self.api = utils.FakeAPI(fake_responses)
|
||||||
|
self.mgr = cluster_templates.ClusterTemplateManager(self.api)
|
||||||
|
|
||||||
|
def test_clustertemplate_list(self):
|
||||||
|
clustertemplates = self.mgr.list()
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clustertemplates', {}, None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertThat(clustertemplates, matchers.HasLength(2))
|
||||||
|
|
||||||
|
def _test_clustertemplate_list_with_filters(
|
||||||
|
self, limit=None, marker=None,
|
||||||
|
sort_key=None, sort_dir=None,
|
||||||
|
detail=False, expect=[]):
|
||||||
|
clustertemplates_filter = self.mgr.list(limit=limit,
|
||||||
|
marker=marker,
|
||||||
|
sort_key=sort_key,
|
||||||
|
sort_dir=sort_dir,
|
||||||
|
detail=detail)
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertThat(clustertemplates_filter, matchers.HasLength(2))
|
||||||
|
|
||||||
|
def test_clustertemplate_list_with_detail(self):
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clustertemplates/detail', {}, None),
|
||||||
|
]
|
||||||
|
self._test_clustertemplate_list_with_filters(
|
||||||
|
detail=True,
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_clustertemplate_list_with_limit(self):
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clustertemplates/?limit=2', {}, None),
|
||||||
|
]
|
||||||
|
self._test_clustertemplate_list_with_filters(
|
||||||
|
limit=2,
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_clustertemplate_list_with_marker(self):
|
||||||
|
expect = [
|
||||||
|
('GET',
|
||||||
|
'/v1/clustertemplates/?marker=%s' % CLUSTERTEMPLATE2['uuid'],
|
||||||
|
{},
|
||||||
|
None),
|
||||||
|
]
|
||||||
|
self._test_clustertemplate_list_with_filters(
|
||||||
|
marker=CLUSTERTEMPLATE2['uuid'],
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_clustertemplate_list_with_marker_limit(self):
|
||||||
|
expect = [
|
||||||
|
('GET',
|
||||||
|
'/v1/clustertemplates/?limit=2&marker=%s' %
|
||||||
|
CLUSTERTEMPLATE2['uuid'],
|
||||||
|
{},
|
||||||
|
None),
|
||||||
|
]
|
||||||
|
self._test_clustertemplate_list_with_filters(
|
||||||
|
limit=2, marker=CLUSTERTEMPLATE2['uuid'],
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_clustertemplate_list_with_sort_dir(self):
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clustertemplates/?sort_dir=asc', {}, None),
|
||||||
|
]
|
||||||
|
self._test_clustertemplate_list_with_filters(
|
||||||
|
sort_dir='asc',
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_clustertemplate_list_with_sort_key(self):
|
||||||
|
expect = [
|
||||||
|
('GET', '/v1/clustertemplates/?sort_key=uuid', {}, None),
|
||||||
|
]
|
||||||
|
self._test_clustertemplate_list_with_filters(
|
||||||
|
sort_key='uuid',
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_clustertemplate_list_with_sort_key_dir(self):
|
||||||
|
expect = [
|
||||||
|
('GET',
|
||||||
|
'/v1/clustertemplates/?sort_key=uuid&sort_dir=desc',
|
||||||
|
{},
|
||||||
|
None),
|
||||||
|
]
|
||||||
|
self._test_clustertemplate_list_with_filters(
|
||||||
|
sort_key='uuid', sort_dir='desc',
|
||||||
|
expect=expect)
|
||||||
|
|
||||||
|
def test_clustertemplate_show_by_id(self):
|
||||||
|
cluster_template = self.mgr.get(CLUSTERTEMPLATE1['id'])
|
||||||
|
expect = [
|
||||||
|
('GET',
|
||||||
|
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id'],
|
||||||
|
{},
|
||||||
|
None)
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['name'],
|
||||||
|
cluster_template.name)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['image_id'],
|
||||||
|
cluster_template.image_id)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'],
|
||||||
|
cluster_template.docker_volume_size)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'],
|
||||||
|
cluster_template.docker_storage_driver)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['fixed_network'],
|
||||||
|
cluster_template.fixed_network)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['fixed_subnet'],
|
||||||
|
cluster_template.fixed_subnet)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['coe'],
|
||||||
|
cluster_template.coe)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['http_proxy'],
|
||||||
|
cluster_template.http_proxy)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['https_proxy'],
|
||||||
|
cluster_template.https_proxy)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['no_proxy'],
|
||||||
|
cluster_template.no_proxy)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['network_driver'],
|
||||||
|
cluster_template.network_driver)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['volume_driver'],
|
||||||
|
cluster_template.volume_driver)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['labels'],
|
||||||
|
cluster_template.labels)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['tls_disabled'],
|
||||||
|
cluster_template.tls_disabled)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['public'],
|
||||||
|
cluster_template.public)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['registry_enabled'],
|
||||||
|
cluster_template.registry_enabled)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['master_lb_enabled'],
|
||||||
|
cluster_template.master_lb_enabled)
|
||||||
|
|
||||||
|
def test_clustertemplate_show_by_name(self):
|
||||||
|
cluster_template = self.mgr.get(CLUSTERTEMPLATE1['name'])
|
||||||
|
expect = [
|
||||||
|
('GET',
|
||||||
|
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['name'],
|
||||||
|
{},
|
||||||
|
None)
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['name'],
|
||||||
|
cluster_template.name)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['image_id'],
|
||||||
|
cluster_template.image_id)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'],
|
||||||
|
cluster_template.docker_volume_size)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'],
|
||||||
|
cluster_template.docker_storage_driver)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['fixed_network'],
|
||||||
|
cluster_template.fixed_network)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['fixed_subnet'],
|
||||||
|
cluster_template.fixed_subnet)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['coe'],
|
||||||
|
cluster_template.coe)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['http_proxy'],
|
||||||
|
cluster_template.http_proxy)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['https_proxy'],
|
||||||
|
cluster_template.https_proxy)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['no_proxy'],
|
||||||
|
cluster_template.no_proxy)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['network_driver'],
|
||||||
|
cluster_template.network_driver)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['volume_driver'],
|
||||||
|
cluster_template.volume_driver)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['labels'],
|
||||||
|
cluster_template.labels)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['tls_disabled'],
|
||||||
|
cluster_template.tls_disabled)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['public'],
|
||||||
|
cluster_template.public)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['registry_enabled'],
|
||||||
|
cluster_template.registry_enabled)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['master_lb_enabled'],
|
||||||
|
cluster_template.master_lb_enabled)
|
||||||
|
|
||||||
|
def test_clustertemplate_create(self):
|
||||||
|
cluster_template = self.mgr.create(**CREATE_CLUSTERTEMPLATE)
|
||||||
|
expect = [
|
||||||
|
('POST', '/v1/clustertemplates', {}, CREATE_CLUSTERTEMPLATE),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertTrue(cluster_template)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['docker_volume_size'],
|
||||||
|
cluster_template.docker_volume_size)
|
||||||
|
self.assertEqual(CLUSTERTEMPLATE1['docker_storage_driver'],
|
||||||
|
cluster_template.docker_storage_driver)
|
||||||
|
|
||||||
|
def test_clustertemplate_create_fail(self):
|
||||||
|
CREATE_CLUSTERTEMPLATE_FAIL = copy.deepcopy(CREATE_CLUSTERTEMPLATE)
|
||||||
|
CREATE_CLUSTERTEMPLATE_FAIL["wrong_key"] = "wrong"
|
||||||
|
self.assertRaisesRegexp(
|
||||||
|
exceptions.InvalidAttribute,
|
||||||
|
("Key must be in %s" %
|
||||||
|
','.join(cluster_templates.CREATION_ATTRIBUTES)),
|
||||||
|
self.mgr.create, **CREATE_CLUSTERTEMPLATE_FAIL)
|
||||||
|
self.assertEqual([], self.api.calls)
|
||||||
|
|
||||||
|
def test_clustertemplate_delete_by_id(self):
|
||||||
|
cluster_template = self.mgr.delete(CLUSTERTEMPLATE1['id'])
|
||||||
|
expect = [
|
||||||
|
('DELETE',
|
||||||
|
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id'],
|
||||||
|
{},
|
||||||
|
None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertIsNone(cluster_template)
|
||||||
|
|
||||||
|
def test_clustertemplate_delete_by_name(self):
|
||||||
|
cluster_template = self.mgr.delete(CLUSTERTEMPLATE1['name'])
|
||||||
|
expect = [
|
||||||
|
('DELETE',
|
||||||
|
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['name'],
|
||||||
|
{},
|
||||||
|
None),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertIsNone(cluster_template)
|
||||||
|
|
||||||
|
def test_clustertemplate_update(self):
|
||||||
|
patch = {'op': 'replace',
|
||||||
|
'value': NEW_NAME,
|
||||||
|
'path': '/name'}
|
||||||
|
cluster_template = self.mgr.update(id=CLUSTERTEMPLATE1['id'],
|
||||||
|
patch=patch)
|
||||||
|
expect = [
|
||||||
|
('PATCH',
|
||||||
|
'/v1/clustertemplates/%s' % CLUSTERTEMPLATE1['id'],
|
||||||
|
{},
|
||||||
|
patch),
|
||||||
|
]
|
||||||
|
self.assertEqual(expect, self.api.calls)
|
||||||
|
self.assertEqual(NEW_NAME, cluster_template.name)
|
408
magnumclient/tests/v1/test_clustertemplates_shell.py
Normal file
408
magnumclient/tests/v1/test_clustertemplates_shell.py
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
# Copyright 2015 NEC Corporation. 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.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
|
||||||
|
from magnumclient.common.apiclient import exceptions
|
||||||
|
from magnumclient.tests.v1 import shell_test_base
|
||||||
|
from magnumclient.v1.cluster_templates import ClusterTemplate
|
||||||
|
|
||||||
|
|
||||||
|
class FakeClusterTemplate(ClusterTemplate):
|
||||||
|
def __init__(self, manager=None, info={}, **kwargs):
|
||||||
|
ClusterTemplate.__init__(self, manager=manager, info=info)
|
||||||
|
self.apiserver_port = kwargs.get('apiserver_port', None)
|
||||||
|
self.uuid = kwargs.get('uuid', 'x')
|
||||||
|
self.links = kwargs.get('links', [])
|
||||||
|
self.server_type = kwargs.get('server_type', 'vm')
|
||||||
|
self.image_id = kwargs.get('image_id', 'x')
|
||||||
|
self.tls_disabled = kwargs.get('tls_disabled', False)
|
||||||
|
self.registry_enabled = kwargs.get('registry_enabled', False)
|
||||||
|
self.coe = kwargs.get('coe', 'x')
|
||||||
|
self.public = kwargs.get('public', False)
|
||||||
|
self.name = kwargs.get('name', 'x')
|
||||||
|
|
||||||
|
|
||||||
|
class ShellTest(shell_test_base.TestCommandLineArgument):
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_success(self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--coe swarm '
|
||||||
|
'--dns-nameserver test_dns '
|
||||||
|
'--flavor-id test_flavor '
|
||||||
|
'--fixed-network private '
|
||||||
|
'--fixed-network private-subnet '
|
||||||
|
'--volume-driver test_volume '
|
||||||
|
'--network-driver test_driver '
|
||||||
|
'--labels key=val '
|
||||||
|
'--master-flavor-id test_flavor '
|
||||||
|
'--docker-volume-size 10 '
|
||||||
|
'--docker-storage-driver devicemapper '
|
||||||
|
'--public '
|
||||||
|
'--server-type vm'
|
||||||
|
'--master-lb-enabled')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe kubernetes '
|
||||||
|
'--name test '
|
||||||
|
'--server-type vm')
|
||||||
|
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_success_no_servertype(self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--coe swarm '
|
||||||
|
'--dns-nameserver test_dns '
|
||||||
|
'--flavor-id test_flavor '
|
||||||
|
'--fixed-network public '
|
||||||
|
'--network-driver test_driver '
|
||||||
|
'--labels key=val '
|
||||||
|
'--master-flavor-id test_flavor '
|
||||||
|
'--docker-volume-size 10 '
|
||||||
|
'--docker-storage-driver devicemapper '
|
||||||
|
'--public ')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe kubernetes '
|
||||||
|
'--name test ')
|
||||||
|
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_success_with_registry_enabled(
|
||||||
|
self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test '
|
||||||
|
'--network-driver test_driver '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--registry-enabled')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_public_success(self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test --network-driver test_driver '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm'
|
||||||
|
'--public '
|
||||||
|
'--server-type vm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_success_with_master_flavor(self,
|
||||||
|
mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--coe swarm '
|
||||||
|
'--dns-nameserver test_dns '
|
||||||
|
'--master-flavor-id test_flavor')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_docker_vol_size_success(self,
|
||||||
|
mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test --docker-volume-size 4514 '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--server-type vm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_docker_storage_driver_success(
|
||||||
|
self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--docker-storage-driver devicemapper '
|
||||||
|
'--coe swarm'
|
||||||
|
)
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_fixed_network_success(self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test --fixed-network private '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--server-type vm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_network_driver_success(self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test --network-driver test_driver '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--server-type vm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_volume_driver_success(self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test --volume-driver test_volume '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--server-type vm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_http_proxy_success(self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test --fixed-network private '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--http-proxy http_proxy '
|
||||||
|
'--server-type vm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_https_proxy_success(self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test --fixed-network private '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--https-proxy https_proxy '
|
||||||
|
'--server-type vm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_no_proxy_success(self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test --fixed-network private '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--no-proxy no_proxy '
|
||||||
|
'--server-type vm')
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_labels_success(self, mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test '
|
||||||
|
'--labels key=val '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--server-type vm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_separate_labels_success(self,
|
||||||
|
mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test '
|
||||||
|
'--labels key1=val1 '
|
||||||
|
'--labels key2=val2 '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--server-type vm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_combined_labels_success(self,
|
||||||
|
mock_create):
|
||||||
|
self._test_arg_success('cluster-template-create '
|
||||||
|
'--name test '
|
||||||
|
'--labels key1=val1,key2=val2 '
|
||||||
|
'--keypair-id test_keypair '
|
||||||
|
'--external-network-id test_net '
|
||||||
|
'--image-id test_image '
|
||||||
|
'--coe swarm '
|
||||||
|
'--server-type vm')
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.create')
|
||||||
|
def test_cluster_template_create_failure_few_arg(self, mock_create):
|
||||||
|
self._test_arg_failure('cluster-template-create '
|
||||||
|
'--name test', self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_failure('cluster-template-create '
|
||||||
|
'--image-id test', self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_failure('cluster-template-create '
|
||||||
|
'--keypair-id test', self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_failure('cluster-template-create '
|
||||||
|
'--external-network-id test',
|
||||||
|
self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_failure('cluster-template-create '
|
||||||
|
'--coe test', self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_failure('cluster-template-create '
|
||||||
|
'--server-type test', self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
self._test_arg_failure('cluster-template-create',
|
||||||
|
self._mandatory_arg_error)
|
||||||
|
self.assertFalse(mock_create.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||||
|
def test_cluster_template_show_success(self, mock_show):
|
||||||
|
self._test_arg_success('cluster-template-show xxx')
|
||||||
|
self.assertTrue(mock_show.called)
|
||||||
|
|
||||||
|
@mock.patch('magnumclient.v1.cluster_templates.ClusterTemplateManager.get')
|
||||||
|
def test_cluster_template_show_failure_no_arg(self, mock_show):
|
||||||
|
self._test_arg_failure('cluster-template-show',
|
||||||
|
self._few_argument_error)
|
||||||
|
self.assertFalse(mock_show.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.delete')
|
||||||
|
def test_cluster_template_delete_success(self, mock_delete):
|
||||||
|
self._test_arg_success('cluster-template-delete xxx')
|
||||||
|
self.assertTrue(mock_delete.called)
|
||||||
|
self.assertEqual(1, mock_delete.call_count)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.delete')
|
||||||
|
def test_cluster_template_delete_multiple_id_success(self, mock_delete):
|
||||||
|
self._test_arg_success('cluster-template-delete xxx xyz')
|
||||||
|
self.assertTrue(mock_delete.called)
|
||||||
|
self.assertEqual(2, mock_delete.call_count)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.delete')
|
||||||
|
def test_cluster_template_delete_failure_no_arg(self, mock_delete):
|
||||||
|
self._test_arg_failure('cluster-template-delete',
|
||||||
|
self._few_argument_error)
|
||||||
|
self.assertFalse(mock_delete.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||||
|
def test_cluster_template_list_success(self, mock_list):
|
||||||
|
self._test_arg_success('cluster-template-list')
|
||||||
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||||
|
def test_cluster_template_list_success_with_arg(self, mock_list):
|
||||||
|
self._test_arg_success('cluster-template-list '
|
||||||
|
'--limit 1 '
|
||||||
|
'--sort-dir asc '
|
||||||
|
'--sort-key uuid')
|
||||||
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||||
|
def test_cluster_template_list_ignored_duplicated_field(self, mock_list):
|
||||||
|
mock_list.return_value = [FakeClusterTemplate()]
|
||||||
|
self._test_arg_success(
|
||||||
|
'cluster-template-list --fields coe,coe,coe,name,name',
|
||||||
|
keyword='\n| uuid | name | Coe |\n')
|
||||||
|
# Output should be
|
||||||
|
# +------+------+-----+
|
||||||
|
# | uuid | name | Coe |
|
||||||
|
# +------+------+-----+
|
||||||
|
# | x | x | x |
|
||||||
|
# +------+------+-----+
|
||||||
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||||
|
def test_cluster_template_list_failure_with_invalid_field(self, mock_list):
|
||||||
|
mock_list.return_value = [FakeClusterTemplate()]
|
||||||
|
_error_msg = [".*?^Non-existent fields are specified: ['xxx','zzz']"]
|
||||||
|
self.assertRaises(exceptions.CommandError,
|
||||||
|
self._test_arg_failure,
|
||||||
|
'cluster-template-list --fields xxx,coe,zzz',
|
||||||
|
_error_msg)
|
||||||
|
self.assertTrue(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||||
|
def test_cluster_template_list_failure_invalid_arg(self, mock_list):
|
||||||
|
_error_msg = [
|
||||||
|
'.*?^usage: magnum cluster-template-list ',
|
||||||
|
'.*?^error: argument --sort-dir: invalid choice: ',
|
||||||
|
".*?^Try 'magnum help cluster-template-list' for more information."
|
||||||
|
]
|
||||||
|
self._test_arg_failure('cluster-template-list --sort-dir aaa',
|
||||||
|
_error_msg)
|
||||||
|
self.assertFalse(mock_list.called)
|
||||||
|
|
||||||
|
@mock.patch(
|
||||||
|
'magnumclient.v1.cluster_templates.ClusterTemplateManager.list')
|
||||||
|
def test_cluster_template_list_failure(self, mock_list):
|
||||||
|
self._test_arg_failure('cluster-template-list --wrong',
|
||||||
|
self._unrecognized_arg_error)
|
||||||
|
self.assertFalse(mock_list.called)
|
112
magnumclient/v1/basemodels.py
Normal file
112
magnumclient/v1/basemodels.py
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from magnumclient.common import base
|
||||||
|
from magnumclient.common import utils
|
||||||
|
from magnumclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
CREATION_ATTRIBUTES = ['name', 'image_id', 'flavor_id', 'master_flavor_id',
|
||||||
|
'keypair_id', 'external_network_id', 'fixed_network',
|
||||||
|
'fixed_subnet', 'dns_nameserver', 'docker_volume_size',
|
||||||
|
'labels', 'coe', 'http_proxy', 'https_proxy',
|
||||||
|
'no_proxy', 'network_driver', 'tls_disabled', 'public',
|
||||||
|
'registry_enabled', 'volume_driver', 'server_type',
|
||||||
|
'docker_storage_driver', 'master_lb_enabled']
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModel(base.Resource):
|
||||||
|
# model_name needs to be overridden by any derived class.
|
||||||
|
# model_name should be capitalized and singular, e.g. "Cluster"
|
||||||
|
model_name = ''
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<" + self.__class__.model_name + "%s>" % self._info
|
||||||
|
|
||||||
|
|
||||||
|
class BaseModelManager(base.Manager):
|
||||||
|
# api_name needs to be overridden by any derived class.
|
||||||
|
# api_name should be pluralized and lowercase, e.g. "clustertemplates", as
|
||||||
|
# it shows up in the URL path: "/v1/{api_name}"
|
||||||
|
api_name = ''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _path(cls, id=None):
|
||||||
|
return '/v1/' + cls.api_name + \
|
||||||
|
'/%s' % id if id else '/v1/' + cls.api_name
|
||||||
|
|
||||||
|
def list(self, limit=None, marker=None, sort_key=None,
|
||||||
|
sort_dir=None, detail=False):
|
||||||
|
"""Retrieve a list of baymodels.
|
||||||
|
|
||||||
|
:param marker: Optional, the UUID of a baymodel, eg the last
|
||||||
|
baymodel from a previous result set. Return
|
||||||
|
the next result set.
|
||||||
|
:param limit: The maximum number of results to return per
|
||||||
|
request, if:
|
||||||
|
|
||||||
|
1) limit > 0, the maximum number of baymodels to return.
|
||||||
|
2) limit == 0, return the entire list of baymodels.
|
||||||
|
3) limit param is NOT specified (None), the number of items
|
||||||
|
returned respect the maximum imposed by the Magnum API
|
||||||
|
(see Magnum's api.max_limit option).
|
||||||
|
|
||||||
|
:param sort_key: Optional, field used for sorting.
|
||||||
|
|
||||||
|
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||||
|
default) or 'desc'.
|
||||||
|
|
||||||
|
:param detail: Optional, boolean whether to return detailed information
|
||||||
|
about baymodels.
|
||||||
|
|
||||||
|
:returns: A list of baymodels.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if limit is not None:
|
||||||
|
limit = int(limit)
|
||||||
|
|
||||||
|
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
path = ''
|
||||||
|
if detail:
|
||||||
|
path += 'detail'
|
||||||
|
if filters:
|
||||||
|
path += '?' + '&'.join(filters)
|
||||||
|
|
||||||
|
if limit is None:
|
||||||
|
return self._list(self._path(path), self.__class__.api_name)
|
||||||
|
else:
|
||||||
|
return self._list_pagination(self._path(path),
|
||||||
|
self.__class__.api_name,
|
||||||
|
limit=limit)
|
||||||
|
|
||||||
|
def get(self, id):
|
||||||
|
try:
|
||||||
|
return self._list(self._path(id))[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create(self, **kwargs):
|
||||||
|
new = {}
|
||||||
|
for (key, value) in kwargs.items():
|
||||||
|
if key in CREATION_ATTRIBUTES:
|
||||||
|
new[key] = value
|
||||||
|
else:
|
||||||
|
raise exceptions.InvalidAttribute(
|
||||||
|
"Key must be in %s" % ",".join(CREATION_ATTRIBUTES))
|
||||||
|
return self._create(self._path(), new)
|
||||||
|
|
||||||
|
def delete(self, id):
|
||||||
|
return self._delete(self._path(id))
|
||||||
|
|
||||||
|
def update(self, id, patch):
|
||||||
|
return self._update(self._path(id), patch)
|
108
magnumclient/v1/baseunit.py
Normal file
108
magnumclient/v1/baseunit.py
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
# Copyright 2014 NEC Corporation. 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.
|
||||||
|
|
||||||
|
from magnumclient.common import base
|
||||||
|
from magnumclient.common import utils
|
||||||
|
from magnumclient import exceptions
|
||||||
|
|
||||||
|
|
||||||
|
# Derived classes may append their own custom attributes to this default list
|
||||||
|
CREATION_ATTRIBUTES = ['name', 'node_count', 'discovery_url', 'master_count']
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTemplate(base.Resource):
|
||||||
|
# template_name must be overridden by any derived class.
|
||||||
|
# template_name should be an uppercase plural, e.g. "Clusters"
|
||||||
|
template_name = ''
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "<" + self.__class__.template_name + " %s>" % self._info
|
||||||
|
|
||||||
|
|
||||||
|
class BaseTemplateManager(base.Manager):
|
||||||
|
# template_name must be overridden by any derived class.
|
||||||
|
# template_name should be a lowercase plural, e.g. "clusters"
|
||||||
|
template_name = ''
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _path(cls, id=None):
|
||||||
|
return '/v1/' + cls.template_name + \
|
||||||
|
'/%s' % id if id else '/v1/' + cls.template_name
|
||||||
|
|
||||||
|
def list(self, limit=None, marker=None, sort_key=None,
|
||||||
|
sort_dir=None, detail=False):
|
||||||
|
"""Retrieve a list of bays.
|
||||||
|
|
||||||
|
:param marker: Optional, the UUID of a bay, eg the last
|
||||||
|
bay from a previous result set. Return
|
||||||
|
the next result set.
|
||||||
|
:param limit: The maximum number of results to return per
|
||||||
|
request, if:
|
||||||
|
|
||||||
|
1) limit > 0, the maximum number of bays to return.
|
||||||
|
2) limit == 0, return the entire list of bays.
|
||||||
|
3) limit param is NOT specified (None), the number of items
|
||||||
|
returned respect the maximum imposed by the Magnum API
|
||||||
|
(see Magnum's api.max_limit option).
|
||||||
|
|
||||||
|
:param sort_key: Optional, field used for sorting.
|
||||||
|
|
||||||
|
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
||||||
|
default) or 'desc'.
|
||||||
|
|
||||||
|
:param detail: Optional, boolean whether to return detailed information
|
||||||
|
about bays.
|
||||||
|
|
||||||
|
:returns: A list of bays.
|
||||||
|
|
||||||
|
"""
|
||||||
|
if limit is not None:
|
||||||
|
limit = int(limit)
|
||||||
|
|
||||||
|
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
||||||
|
|
||||||
|
path = ''
|
||||||
|
if detail:
|
||||||
|
path += 'detail'
|
||||||
|
if filters:
|
||||||
|
path += '?' + '&'.join(filters)
|
||||||
|
|
||||||
|
if limit is None:
|
||||||
|
return self._list(self._path(path), self.__class__.template_name)
|
||||||
|
else:
|
||||||
|
return self._list_pagination(self._path(path),
|
||||||
|
self.__class__.template_name,
|
||||||
|
limit=limit)
|
||||||
|
|
||||||
|
def get(self, id):
|
||||||
|
try:
|
||||||
|
return self._list(self._path(id))[0]
|
||||||
|
except IndexError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def create(self, **kwargs):
|
||||||
|
new = {}
|
||||||
|
for (key, value) in kwargs.items():
|
||||||
|
if key in CREATION_ATTRIBUTES:
|
||||||
|
new[key] = value
|
||||||
|
else:
|
||||||
|
raise exceptions.InvalidAttribute(
|
||||||
|
"Key must be in %s" % ",".join(CREATION_ATTRIBUTES))
|
||||||
|
return self._create(self._path(), new)
|
||||||
|
|
||||||
|
def delete(self, id):
|
||||||
|
return self._delete(self._path(id))
|
||||||
|
|
||||||
|
def update(self, id, patch):
|
||||||
|
return self._update(self._path(id), patch)
|
@ -10,94 +10,16 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from magnumclient.common import base
|
from magnumclient.v1 import basemodels
|
||||||
from magnumclient.common import utils
|
|
||||||
from magnumclient import exceptions
|
|
||||||
|
|
||||||
|
|
||||||
CREATION_ATTRIBUTES = ['name', 'image_id', 'flavor_id', 'master_flavor_id',
|
CREATION_ATTRIBUTES = basemodels.CREATION_ATTRIBUTES
|
||||||
'keypair_id', 'external_network_id', 'fixed_network',
|
|
||||||
'fixed_subnet', 'dns_nameserver', 'docker_volume_size',
|
|
||||||
'labels', 'coe', 'http_proxy', 'https_proxy',
|
|
||||||
'no_proxy', 'network_driver', 'tls_disabled', 'public',
|
|
||||||
'registry_enabled', 'volume_driver', 'server_type',
|
|
||||||
'docker_storage_driver', 'master_lb_enabled']
|
|
||||||
|
|
||||||
|
|
||||||
class BayModel(base.Resource):
|
class BayModel(basemodels.BaseModel):
|
||||||
def __repr__(self):
|
model_name = "BayModel"
|
||||||
return "<BayModel %s>" % self._info
|
|
||||||
|
|
||||||
|
|
||||||
class BayModelManager(base.Manager):
|
class BayModelManager(basemodels.BaseModelManager):
|
||||||
|
api_name = "baymodels"
|
||||||
resource_class = BayModel
|
resource_class = BayModel
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _path(id=None):
|
|
||||||
return '/v1/baymodels/%s' % id if id else '/v1/baymodels'
|
|
||||||
|
|
||||||
def list(self, limit=None, marker=None, sort_key=None,
|
|
||||||
sort_dir=None, detail=False):
|
|
||||||
"""Retrieve a list of baymodels.
|
|
||||||
|
|
||||||
:param marker: Optional, the UUID of a baymodel, eg the last
|
|
||||||
baymodel from a previous result set. Return
|
|
||||||
the next result set.
|
|
||||||
:param limit: The maximum number of results to return per
|
|
||||||
request, if:
|
|
||||||
|
|
||||||
1) limit > 0, the maximum number of baymodels to return.
|
|
||||||
2) limit == 0, return the entire list of baymodels.
|
|
||||||
3) limit param is NOT specified (None), the number of items
|
|
||||||
returned respect the maximum imposed by the Magnum API
|
|
||||||
(see Magnum's api.max_limit option).
|
|
||||||
|
|
||||||
:param sort_key: Optional, field used for sorting.
|
|
||||||
|
|
||||||
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
|
||||||
default) or 'desc'.
|
|
||||||
|
|
||||||
:param detail: Optional, boolean whether to return detailed information
|
|
||||||
about baymodels.
|
|
||||||
|
|
||||||
:returns: A list of baymodels.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if limit is not None:
|
|
||||||
limit = int(limit)
|
|
||||||
|
|
||||||
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
|
||||||
|
|
||||||
path = ''
|
|
||||||
if detail:
|
|
||||||
path += 'detail'
|
|
||||||
if filters:
|
|
||||||
path += '?' + '&'.join(filters)
|
|
||||||
|
|
||||||
if limit is None:
|
|
||||||
return self._list(self._path(path), "baymodels")
|
|
||||||
else:
|
|
||||||
return self._list_pagination(self._path(path), "baymodels",
|
|
||||||
limit=limit)
|
|
||||||
|
|
||||||
def get(self, id):
|
|
||||||
try:
|
|
||||||
return self._list(self._path(id))[0]
|
|
||||||
except IndexError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def create(self, **kwargs):
|
|
||||||
new = {}
|
|
||||||
for (key, value) in kwargs.items():
|
|
||||||
if key in CREATION_ATTRIBUTES:
|
|
||||||
new[key] = value
|
|
||||||
else:
|
|
||||||
raise exceptions.InvalidAttribute(
|
|
||||||
"Key must be in %s" % ",".join(CREATION_ATTRIBUTES))
|
|
||||||
return self._create(self._path(), new)
|
|
||||||
|
|
||||||
def delete(self, id):
|
|
||||||
return self._delete(self._path(id))
|
|
||||||
|
|
||||||
def update(self, id, patch):
|
|
||||||
return self._update(self._path(id), patch)
|
|
||||||
|
@ -19,6 +19,11 @@ from magnumclient.common import utils as magnum_utils
|
|||||||
from magnumclient.i18n import _
|
from magnumclient.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
DEPRECATION_MESSAGE = (
|
||||||
|
'WARNING: Baymodel commands are deprecated and will be removed in a future'
|
||||||
|
' release.\nUse cluster commands to avoid seeing this message.')
|
||||||
|
|
||||||
|
|
||||||
def _show_baymodel(baymodel):
|
def _show_baymodel(baymodel):
|
||||||
del baymodel._info['links']
|
del baymodel._info['links']
|
||||||
utils.print_dict(baymodel._info)
|
utils.print_dict(baymodel._info)
|
||||||
@ -115,6 +120,7 @@ def _show_baymodel(baymodel):
|
|||||||
action='store_true', default=False,
|
action='store_true', default=False,
|
||||||
help='Indicates whether created bays should have a load balancer '
|
help='Indicates whether created bays should have a load balancer '
|
||||||
'for master nodes or not.')
|
'for master nodes or not.')
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
def do_baymodel_create(cs, args):
|
def do_baymodel_create(cs, args):
|
||||||
"""Create a baymodel."""
|
"""Create a baymodel."""
|
||||||
opts = {}
|
opts = {}
|
||||||
@ -150,6 +156,7 @@ def do_baymodel_create(cs, args):
|
|||||||
metavar='<baymodels>',
|
metavar='<baymodels>',
|
||||||
nargs='+',
|
nargs='+',
|
||||||
help='ID or name of the (baymodel)s to delete.')
|
help='ID or name of the (baymodel)s to delete.')
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
def do_baymodel_delete(cs, args):
|
def do_baymodel_delete(cs, args):
|
||||||
"""Delete specified baymodel."""
|
"""Delete specified baymodel."""
|
||||||
for baymodel in args.baymodels:
|
for baymodel in args.baymodels:
|
||||||
@ -165,6 +172,7 @@ def do_baymodel_delete(cs, args):
|
|||||||
@utils.arg('baymodel',
|
@utils.arg('baymodel',
|
||||||
metavar='<baymodel>',
|
metavar='<baymodel>',
|
||||||
help='ID or name of the baymodel to show.')
|
help='ID or name of the baymodel to show.')
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
def do_baymodel_show(cs, args):
|
def do_baymodel_show(cs, args):
|
||||||
"""Show details about the given baymodel."""
|
"""Show details about the given baymodel."""
|
||||||
baymodel = cs.baymodels.get(args.baymodel)
|
baymodel = cs.baymodels.get(args.baymodel)
|
||||||
@ -190,6 +198,7 @@ def do_baymodel_show(cs, args):
|
|||||||
'apiserver_port, server_type, tls_disabled, registry_enabled'
|
'apiserver_port, server_type, tls_disabled, registry_enabled'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
def do_baymodel_list(cs, args):
|
def do_baymodel_list(cs, args):
|
||||||
"""Print a list of baymodels."""
|
"""Print a list of baymodels."""
|
||||||
nodes = cs.baymodels.list(limit=args.limit,
|
nodes = cs.baymodels.list(limit=args.limit,
|
||||||
@ -218,6 +227,7 @@ def do_baymodel_list(cs, args):
|
|||||||
default=[],
|
default=[],
|
||||||
help="Attributes to add/replace or remove "
|
help="Attributes to add/replace or remove "
|
||||||
"(only PATH is necessary on remove)")
|
"(only PATH is necessary on remove)")
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
def do_baymodel_update(cs, args):
|
def do_baymodel_update(cs, args):
|
||||||
"""Updates one or more baymodel attributes."""
|
"""Updates one or more baymodel attributes."""
|
||||||
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
|
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
|
||||||
|
@ -12,89 +12,18 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
from magnumclient.common import base
|
from magnumclient.v1 import baseunit
|
||||||
from magnumclient.common import utils
|
|
||||||
from magnumclient import exceptions
|
|
||||||
|
|
||||||
|
|
||||||
CREATION_ATTRIBUTES = ['name', 'baymodel_id', 'node_count', 'discovery_url',
|
CREATION_ATTRIBUTES = baseunit.CREATION_ATTRIBUTES
|
||||||
'bay_create_timeout', 'master_count']
|
CREATION_ATTRIBUTES.append('baymodel_id')
|
||||||
|
CREATION_ATTRIBUTES.append('bay_create_timeout')
|
||||||
|
|
||||||
|
|
||||||
class Bay(base.Resource):
|
class Bay(baseunit.BaseTemplate):
|
||||||
def __repr__(self):
|
template_name = "Bays"
|
||||||
return "<Bay %s>" % self._info
|
|
||||||
|
|
||||||
|
|
||||||
class BayManager(base.Manager):
|
class BayManager(baseunit.BaseTemplateManager):
|
||||||
resource_class = Bay
|
resource_class = Bay
|
||||||
|
template_name = 'bays'
|
||||||
@staticmethod
|
|
||||||
def _path(id=None):
|
|
||||||
return '/v1/bays/%s' % id if id else '/v1/bays'
|
|
||||||
|
|
||||||
def list(self, limit=None, marker=None, sort_key=None,
|
|
||||||
sort_dir=None, detail=False):
|
|
||||||
"""Retrieve a list of bays.
|
|
||||||
|
|
||||||
:param marker: Optional, the UUID of a bay, eg the last
|
|
||||||
bay from a previous result set. Return
|
|
||||||
the next result set.
|
|
||||||
:param limit: The maximum number of results to return per
|
|
||||||
request, if:
|
|
||||||
|
|
||||||
1) limit > 0, the maximum number of bays to return.
|
|
||||||
2) limit == 0, return the entire list of bays.
|
|
||||||
3) limit param is NOT specified (None), the number of items
|
|
||||||
returned respect the maximum imposed by the Magnum API
|
|
||||||
(see Magnum's api.max_limit option).
|
|
||||||
|
|
||||||
:param sort_key: Optional, field used for sorting.
|
|
||||||
|
|
||||||
:param sort_dir: Optional, direction of sorting, either 'asc' (the
|
|
||||||
default) or 'desc'.
|
|
||||||
|
|
||||||
:param detail: Optional, boolean whether to return detailed information
|
|
||||||
about bays.
|
|
||||||
|
|
||||||
:returns: A list of bays.
|
|
||||||
|
|
||||||
"""
|
|
||||||
if limit is not None:
|
|
||||||
limit = int(limit)
|
|
||||||
|
|
||||||
filters = utils.common_filters(marker, limit, sort_key, sort_dir)
|
|
||||||
|
|
||||||
path = ''
|
|
||||||
if detail:
|
|
||||||
path += 'detail'
|
|
||||||
if filters:
|
|
||||||
path += '?' + '&'.join(filters)
|
|
||||||
|
|
||||||
if limit is None:
|
|
||||||
return self._list(self._path(path), "bays")
|
|
||||||
else:
|
|
||||||
return self._list_pagination(self._path(path), "bays",
|
|
||||||
limit=limit)
|
|
||||||
|
|
||||||
def get(self, id):
|
|
||||||
try:
|
|
||||||
return self._list(self._path(id))[0]
|
|
||||||
except IndexError:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def create(self, **kwargs):
|
|
||||||
new = {}
|
|
||||||
for (key, value) in kwargs.items():
|
|
||||||
if key in CREATION_ATTRIBUTES:
|
|
||||||
new[key] = value
|
|
||||||
else:
|
|
||||||
raise exceptions.InvalidAttribute(
|
|
||||||
"Key must be in %s" % ",".join(CREATION_ATTRIBUTES))
|
|
||||||
return self._create(self._path(), new)
|
|
||||||
|
|
||||||
def delete(self, id):
|
|
||||||
return self._delete(self._path(id))
|
|
||||||
|
|
||||||
def update(self, id, patch):
|
|
||||||
return self._update(self._path(id), patch)
|
|
||||||
|
@ -26,6 +26,11 @@ from cryptography.x509.oid import NameOID
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
DEPRECATION_MESSAGE = (
|
||||||
|
'WARNING: Bay commands are deprecated and will be removed in a future '
|
||||||
|
'release.\nUse cluster commands to avoid seeing this message.')
|
||||||
|
|
||||||
|
|
||||||
def _show_bay(bay):
|
def _show_bay(bay):
|
||||||
del bay._info['links']
|
del bay._info['links']
|
||||||
utils.print_dict(bay._info)
|
utils.print_dict(bay._info)
|
||||||
@ -55,6 +60,7 @@ def _show_bay(bay):
|
|||||||
'status, master_count, node_count, links, bay_create_timeout'
|
'status, master_count, node_count, links, bay_create_timeout'
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
def do_bay_list(cs, args):
|
def do_bay_list(cs, args):
|
||||||
"""Print a list of available bays."""
|
"""Print a list of available bays."""
|
||||||
bays = cs.bays.list(marker=args.marker, limit=args.limit,
|
bays = cs.bays.list(marker=args.marker, limit=args.limit,
|
||||||
@ -69,6 +75,7 @@ def do_bay_list(cs, args):
|
|||||||
sortby_index=None)
|
sortby_index=None)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
@utils.arg('--name',
|
@utils.arg('--name',
|
||||||
metavar='<name>',
|
metavar='<name>',
|
||||||
help='Name of the bay to create.')
|
help='Name of the bay to create.')
|
||||||
@ -118,6 +125,7 @@ def do_bay_create(cs, args):
|
|||||||
metavar='<bay>',
|
metavar='<bay>',
|
||||||
nargs='+',
|
nargs='+',
|
||||||
help='ID or name of the (bay)s to delete.')
|
help='ID or name of the (bay)s to delete.')
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
def do_bay_delete(cs, args):
|
def do_bay_delete(cs, args):
|
||||||
"""Delete specified bay."""
|
"""Delete specified bay."""
|
||||||
for id in args.bay:
|
for id in args.bay:
|
||||||
@ -136,6 +144,7 @@ def do_bay_delete(cs, args):
|
|||||||
@utils.arg('--long',
|
@utils.arg('--long',
|
||||||
action='store_true', default=False,
|
action='store_true', default=False,
|
||||||
help='Display extra associated Baymodel info.')
|
help='Display extra associated Baymodel info.')
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
def do_bay_show(cs, args):
|
def do_bay_show(cs, args):
|
||||||
"""Show details about the given bay."""
|
"""Show details about the given bay."""
|
||||||
bay = cs.bays.get(args.bay)
|
bay = cs.bays.get(args.bay)
|
||||||
@ -163,6 +172,7 @@ def do_bay_show(cs, args):
|
|||||||
default=[],
|
default=[],
|
||||||
help="Attributes to add/replace or remove "
|
help="Attributes to add/replace or remove "
|
||||||
"(only PATH is necessary on remove)")
|
"(only PATH is necessary on remove)")
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
def do_bay_update(cs, args):
|
def do_bay_update(cs, args):
|
||||||
"""Update information about the given bay."""
|
"""Update information about the given bay."""
|
||||||
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
|
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
|
||||||
@ -180,6 +190,7 @@ def do_bay_update(cs, args):
|
|||||||
@utils.arg('--force',
|
@utils.arg('--force',
|
||||||
action='store_true', default=False,
|
action='store_true', default=False,
|
||||||
help='Overwrite files if existing.')
|
help='Overwrite files if existing.')
|
||||||
|
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||||
def do_bay_config(cs, args):
|
def do_bay_config(cs, args):
|
||||||
"""Configure native client to access bay.
|
"""Configure native client to access bay.
|
||||||
|
|
||||||
@ -227,13 +238,13 @@ def _config_bay_kubernetes(bay, baymodel, cfg_dir, force=False):
|
|||||||
cfg_file = "%s/config" % cfg_dir
|
cfg_file = "%s/config" % cfg_dir
|
||||||
if baymodel.tls_disabled:
|
if baymodel.tls_disabled:
|
||||||
cfg = ("apiVersion: v1\n"
|
cfg = ("apiVersion: v1\n"
|
||||||
"clusters:\n"
|
"bays:\n"
|
||||||
"- cluster:\n"
|
"- bay:\n"
|
||||||
" server: %(api_address)s\n"
|
" server: %(api_address)s\n"
|
||||||
" name: %(name)s\n"
|
" name: %(name)s\n"
|
||||||
"contexts:\n"
|
"contexts:\n"
|
||||||
"- context:\n"
|
"- context:\n"
|
||||||
" cluster: %(name)s\n"
|
" bay: %(name)s\n"
|
||||||
" user: %(name)s\n"
|
" user: %(name)s\n"
|
||||||
" name: default/%(name)s\n"
|
" name: default/%(name)s\n"
|
||||||
"current-context: default/%(name)s\n"
|
"current-context: default/%(name)s\n"
|
||||||
@ -244,14 +255,14 @@ def _config_bay_kubernetes(bay, baymodel, cfg_dir, force=False):
|
|||||||
% {'name': bay.name, 'api_address': bay.api_address})
|
% {'name': bay.name, 'api_address': bay.api_address})
|
||||||
else:
|
else:
|
||||||
cfg = ("apiVersion: v1\n"
|
cfg = ("apiVersion: v1\n"
|
||||||
"clusters:\n"
|
"bays:\n"
|
||||||
"- cluster:\n"
|
"- bay:\n"
|
||||||
" certificate-authority: ca.pem\n"
|
" certificate-authority: ca.pem\n"
|
||||||
" server: %(api_address)s\n"
|
" server: %(api_address)s\n"
|
||||||
" name: %(name)s\n"
|
" name: %(name)s\n"
|
||||||
"contexts:\n"
|
"contexts:\n"
|
||||||
"- context:\n"
|
"- context:\n"
|
||||||
" cluster: %(name)s\n"
|
" bay: %(name)s\n"
|
||||||
" user: %(name)s\n"
|
" user: %(name)s\n"
|
||||||
" name: default/%(name)s\n"
|
" name: default/%(name)s\n"
|
||||||
"current-context: default/%(name)s\n"
|
"current-context: default/%(name)s\n"
|
||||||
|
@ -21,6 +21,8 @@ from magnumclient.common import httpclient
|
|||||||
from magnumclient.v1 import baymodels
|
from magnumclient.v1 import baymodels
|
||||||
from magnumclient.v1 import bays
|
from magnumclient.v1 import bays
|
||||||
from magnumclient.v1 import certificates
|
from magnumclient.v1 import certificates
|
||||||
|
from magnumclient.v1 import cluster_templates
|
||||||
|
from magnumclient.v1 import clusters
|
||||||
from magnumclient.v1 import mservices
|
from magnumclient.v1 import mservices
|
||||||
|
|
||||||
|
|
||||||
@ -145,6 +147,9 @@ class Client(object):
|
|||||||
endpoint_override=endpoint_override,
|
endpoint_override=endpoint_override,
|
||||||
)
|
)
|
||||||
self.bays = bays.BayManager(self.http_client)
|
self.bays = bays.BayManager(self.http_client)
|
||||||
|
self.clusters = clusters.ClusterManager(self.http_client)
|
||||||
self.certificates = certificates.CertificateManager(self.http_client)
|
self.certificates = certificates.CertificateManager(self.http_client)
|
||||||
self.baymodels = baymodels.BayModelManager(self.http_client)
|
self.baymodels = baymodels.BayModelManager(self.http_client)
|
||||||
|
self.cluster_templates = \
|
||||||
|
cluster_templates.ClusterTemplateManager(self.http_client)
|
||||||
self.mservices = mservices.MServiceManager(self.http_client)
|
self.mservices = mservices.MServiceManager(self.http_client)
|
||||||
|
25
magnumclient/v1/cluster_templates.py
Normal file
25
magnumclient/v1/cluster_templates.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# 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.
|
||||||
|
|
||||||
|
from magnumclient.v1 import basemodels
|
||||||
|
|
||||||
|
|
||||||
|
CREATION_ATTRIBUTES = basemodels.CREATION_ATTRIBUTES
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterTemplate(basemodels.BaseModel):
|
||||||
|
model_name = "ClusterTemplate"
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterTemplateManager(basemodels.BaseModelManager):
|
||||||
|
api_name = "clustertemplates"
|
||||||
|
resource_class = ClusterTemplate
|
234
magnumclient/v1/cluster_templates_shell.py
Normal file
234
magnumclient/v1/cluster_templates_shell.py
Normal file
@ -0,0 +1,234 @@
|
|||||||
|
# Copyright 2015 NEC Corporation. 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.
|
||||||
|
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
from magnumclient.common import cliutils as utils
|
||||||
|
from magnumclient.common import utils as magnum_utils
|
||||||
|
from magnumclient.i18n import _
|
||||||
|
|
||||||
|
|
||||||
|
def _show_cluster_template(cluster_template):
|
||||||
|
del cluster_template._info['links']
|
||||||
|
utils.print_dict(cluster_template._info)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('--name',
|
||||||
|
metavar='<name>',
|
||||||
|
help='Name of the cluster template to create.')
|
||||||
|
@utils.arg('--image-id',
|
||||||
|
required=True,
|
||||||
|
metavar='<image-id>',
|
||||||
|
help='The name or UUID of the base image to customize for the bay.')
|
||||||
|
@utils.arg('--keypair-id',
|
||||||
|
required=True,
|
||||||
|
metavar='<keypair-id>',
|
||||||
|
help='The name or UUID of the SSH keypair to load into the'
|
||||||
|
' Bay nodes.')
|
||||||
|
@utils.arg('--external-network-id',
|
||||||
|
required=True,
|
||||||
|
metavar='<external-network-id>',
|
||||||
|
help='The external Neutron network ID to connect to this bay'
|
||||||
|
' model.')
|
||||||
|
@utils.arg('--coe',
|
||||||
|
required=True,
|
||||||
|
metavar='<coe>',
|
||||||
|
help='Specify the Container Orchestration Engine to use.')
|
||||||
|
@utils.arg('--fixed-network',
|
||||||
|
metavar='<fixed-network>',
|
||||||
|
help='The private Neutron network name to connect to this bay'
|
||||||
|
' model.')
|
||||||
|
@utils.arg('--fixed-subnet',
|
||||||
|
metavar='<fixed-subnet>',
|
||||||
|
help='The private Neutron subnet name to connect to bay.')
|
||||||
|
@utils.arg('--network-driver',
|
||||||
|
metavar='<network-driver>',
|
||||||
|
help='The network driver name for instantiating container'
|
||||||
|
' networks.')
|
||||||
|
@utils.arg('--volume-driver',
|
||||||
|
metavar='<volume-driver>',
|
||||||
|
help='The volume driver name for instantiating container'
|
||||||
|
' volume.')
|
||||||
|
@utils.arg('--dns-nameserver',
|
||||||
|
metavar='<dns-nameserver>',
|
||||||
|
default='8.8.8.8',
|
||||||
|
help='The DNS nameserver to use for this cluster template.')
|
||||||
|
@utils.arg('--flavor-id',
|
||||||
|
metavar='<flavor-id>',
|
||||||
|
default='m1.medium',
|
||||||
|
help='The nova flavor id to use when launching the bay.')
|
||||||
|
@utils.arg('--master-flavor-id',
|
||||||
|
metavar='<master-flavor-id>',
|
||||||
|
help='The nova flavor id to use when launching the master node '
|
||||||
|
'of the bay.')
|
||||||
|
@utils.arg('--docker-volume-size',
|
||||||
|
metavar='<docker-volume-size>',
|
||||||
|
type=int,
|
||||||
|
help='Specify the number of size in GB '
|
||||||
|
'for the docker volume to use.')
|
||||||
|
@utils.arg('--docker-storage-driver',
|
||||||
|
metavar='<docker-storage-driver>',
|
||||||
|
default='devicemapper',
|
||||||
|
help='Select a docker storage driver. Supported: devicemapper, '
|
||||||
|
'overlay. Default: devicemapper')
|
||||||
|
@utils.arg('--http-proxy',
|
||||||
|
metavar='<http-proxy>',
|
||||||
|
help='The http_proxy address to use for nodes in bay.')
|
||||||
|
@utils.arg('--https-proxy',
|
||||||
|
metavar='<https-proxy>',
|
||||||
|
help='The https_proxy address to use for nodes in bay.')
|
||||||
|
@utils.arg('--no-proxy',
|
||||||
|
metavar='<no-proxy>',
|
||||||
|
help='The no_proxy address to use for nodes in bay.')
|
||||||
|
@utils.arg('--labels', metavar='<KEY1=VALUE1,KEY2=VALUE2;KEY3=VALUE3...>',
|
||||||
|
action='append', default=[],
|
||||||
|
help='Arbitrary labels in the form of key=value pairs '
|
||||||
|
'to associate with a cluster template. '
|
||||||
|
'May be used multiple times.')
|
||||||
|
@utils.arg('--tls-disabled',
|
||||||
|
action='store_true', default=False,
|
||||||
|
help='Disable TLS in the Bay.')
|
||||||
|
@utils.arg('--public',
|
||||||
|
action='store_true', default=False,
|
||||||
|
help='Make cluster template public.')
|
||||||
|
@utils.arg('--registry-enabled',
|
||||||
|
action='store_true', default=False,
|
||||||
|
help='Enable docker registry in the Bay')
|
||||||
|
@utils.arg('--server-type',
|
||||||
|
metavar='<server-type>',
|
||||||
|
default='vm',
|
||||||
|
help='Specify the server type to be used '
|
||||||
|
'for example vm. For this release '
|
||||||
|
'default server type will be vm.')
|
||||||
|
@utils.arg('--master-lb-enabled',
|
||||||
|
action='store_true', default=False,
|
||||||
|
help='Indicates whether created bays should have a load balancer '
|
||||||
|
'for master nodes or not.')
|
||||||
|
def do_cluster_template_create(cs, args):
|
||||||
|
"""Create a cluster template."""
|
||||||
|
opts = {}
|
||||||
|
opts['name'] = args.name
|
||||||
|
opts['flavor_id'] = args.flavor_id
|
||||||
|
opts['master_flavor_id'] = args.master_flavor_id
|
||||||
|
opts['image_id'] = args.image_id
|
||||||
|
opts['keypair_id'] = args.keypair_id
|
||||||
|
opts['external_network_id'] = args.external_network_id
|
||||||
|
opts['fixed_network'] = args.fixed_network
|
||||||
|
opts['fixed_subnet'] = args.fixed_subnet
|
||||||
|
opts['network_driver'] = args.network_driver
|
||||||
|
opts['volume_driver'] = args.volume_driver
|
||||||
|
opts['dns_nameserver'] = args.dns_nameserver
|
||||||
|
opts['docker_volume_size'] = args.docker_volume_size
|
||||||
|
opts['docker_storage_driver'] = args.docker_storage_driver
|
||||||
|
opts['coe'] = args.coe
|
||||||
|
opts['http_proxy'] = args.http_proxy
|
||||||
|
opts['https_proxy'] = args.https_proxy
|
||||||
|
opts['no_proxy'] = args.no_proxy
|
||||||
|
opts['labels'] = magnum_utils.handle_labels(args.labels)
|
||||||
|
opts['tls_disabled'] = args.tls_disabled
|
||||||
|
opts['public'] = args.public
|
||||||
|
opts['registry_enabled'] = args.registry_enabled
|
||||||
|
opts['server_type'] = args.server_type
|
||||||
|
opts['master_lb_enabled'] = args.master_lb_enabled
|
||||||
|
|
||||||
|
cluster_template = cs.cluster_templates.create(**opts)
|
||||||
|
_show_cluster_template(cluster_template)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('cluster_templates',
|
||||||
|
metavar='<cluster_templates>',
|
||||||
|
nargs='+',
|
||||||
|
help='ID or name of the (cluster template)s to delete.')
|
||||||
|
def do_cluster_template_delete(cs, args):
|
||||||
|
"""Delete specified cluster template."""
|
||||||
|
for cluster_template in args.cluster_templates:
|
||||||
|
try:
|
||||||
|
cs.cluster_templates.delete(cluster_template)
|
||||||
|
print("Request to delete cluster template %s has been accepted." %
|
||||||
|
cluster_template)
|
||||||
|
except Exception as e:
|
||||||
|
print("Delete for cluster template "
|
||||||
|
"%(cluster_template)s failed: %(e)s" %
|
||||||
|
{'cluster_template': cluster_template, 'e': e})
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('cluster_template',
|
||||||
|
metavar='<cluster_template>',
|
||||||
|
help='ID or name of the cluster template to show.')
|
||||||
|
def do_cluster_template_show(cs, args):
|
||||||
|
"""Show details about the given cluster template."""
|
||||||
|
cluster_template = cs.cluster_templates.get(args.cluster_template)
|
||||||
|
_show_cluster_template(cluster_template)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('--limit',
|
||||||
|
metavar='<limit>',
|
||||||
|
type=int,
|
||||||
|
help='Maximum number of cluster templates to return')
|
||||||
|
@utils.arg('--sort-key',
|
||||||
|
metavar='<sort-key>',
|
||||||
|
help='Column to sort results by')
|
||||||
|
@utils.arg('--sort-dir',
|
||||||
|
metavar='<sort-dir>',
|
||||||
|
choices=['desc', 'asc'],
|
||||||
|
help='Direction to sort. "asc" or "desc".')
|
||||||
|
@utils.arg('--fields',
|
||||||
|
default=None,
|
||||||
|
metavar='<fields>',
|
||||||
|
help=_('Comma-separated list of fields to display. '
|
||||||
|
'Available fields: uuid, name, coe, image_id, public, link, '
|
||||||
|
'apiserver_port, server_type, tls_disabled, registry_enabled'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
def do_cluster_template_list(cs, args):
|
||||||
|
"""Print a list of cluster templates."""
|
||||||
|
nodes = cs.cluster_templates.list(limit=args.limit,
|
||||||
|
sort_key=args.sort_key,
|
||||||
|
sort_dir=args.sort_dir)
|
||||||
|
columns = ['uuid', 'name']
|
||||||
|
columns += utils._get_list_table_columns_and_formatters(
|
||||||
|
args.fields, nodes,
|
||||||
|
exclude_fields=(c.lower() for c in columns))[0]
|
||||||
|
utils.print_list(nodes, columns,
|
||||||
|
{'versions': magnum_utils.print_list_field('versions')},
|
||||||
|
sortby_index=None)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('cluster_template',
|
||||||
|
metavar='<cluster_template>',
|
||||||
|
help="UUID or name of cluster template")
|
||||||
|
@utils.arg(
|
||||||
|
'op',
|
||||||
|
metavar='<op>',
|
||||||
|
choices=['add', 'replace', 'remove'],
|
||||||
|
help="Operations: 'add', 'replace' or 'remove'")
|
||||||
|
@utils.arg(
|
||||||
|
'attributes',
|
||||||
|
metavar='<path=value>',
|
||||||
|
nargs='+',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
help="Attributes to add/replace or remove "
|
||||||
|
"(only PATH is necessary on remove)")
|
||||||
|
def do_cluster_template_update(cs, args):
|
||||||
|
"""Updates one or more cluster template attributes."""
|
||||||
|
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
|
||||||
|
p = patch[0]
|
||||||
|
if p['path'] == '/manifest' and os.path.isfile(p['value']):
|
||||||
|
with open(p['value'], 'r') as f:
|
||||||
|
p['value'] = f.read()
|
||||||
|
|
||||||
|
cluster_template = cs.cluster_templates.update(args.cluster_template,
|
||||||
|
patch)
|
||||||
|
_show_cluster_template(cluster_template)
|
29
magnumclient/v1/clusters.py
Normal file
29
magnumclient/v1/clusters.py
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# Copyright 2014 NEC Corporation. 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.
|
||||||
|
|
||||||
|
from magnumclient.v1 import baseunit
|
||||||
|
|
||||||
|
|
||||||
|
CREATION_ATTRIBUTES = baseunit.CREATION_ATTRIBUTES
|
||||||
|
CREATION_ATTRIBUTES.append('cluster_template_id')
|
||||||
|
CREATION_ATTRIBUTES.append('create_timeout')
|
||||||
|
|
||||||
|
|
||||||
|
class Cluster(baseunit.BaseTemplate):
|
||||||
|
template_name = "Clusters"
|
||||||
|
|
||||||
|
|
||||||
|
class ClusterManager(baseunit.BaseTemplateManager):
|
||||||
|
resource_class = Cluster
|
||||||
|
template_name = 'clusters'
|
326
magnumclient/v1/clusters_shell.py
Normal file
326
magnumclient/v1/clusters_shell.py
Normal file
@ -0,0 +1,326 @@
|
|||||||
|
# Copyright 2015 NEC Corporation. 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.
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from magnumclient.common import cliutils as utils
|
||||||
|
from magnumclient.common import utils as magnum_utils
|
||||||
|
from magnumclient import exceptions
|
||||||
|
from magnumclient.i18n import _
|
||||||
|
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
|
from cryptography.hazmat.primitives import hashes
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.x509.oid import NameOID
|
||||||
|
|
||||||
|
|
||||||
|
def _show_cluster(cluster):
|
||||||
|
del cluster._info['links']
|
||||||
|
utils.print_dict(cluster._info)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('--marker',
|
||||||
|
metavar='<marker>',
|
||||||
|
default=None,
|
||||||
|
help='The last cluster UUID of the previous page; '
|
||||||
|
'displays list of clusters after "marker".')
|
||||||
|
@utils.arg('--limit',
|
||||||
|
metavar='<limit>',
|
||||||
|
type=int,
|
||||||
|
help='Maximum number of clusters to return.')
|
||||||
|
@utils.arg('--sort-key',
|
||||||
|
metavar='<sort-key>',
|
||||||
|
help='Column to sort results by.')
|
||||||
|
@utils.arg('--sort-dir',
|
||||||
|
metavar='<sort-dir>',
|
||||||
|
choices=['desc', 'asc'],
|
||||||
|
help='Direction to sort. "asc" or "desc".')
|
||||||
|
@utils.arg('--fields',
|
||||||
|
default=None,
|
||||||
|
metavar='<fields>',
|
||||||
|
help=_('Comma-separated list of fields to display. '
|
||||||
|
'Available fields: uuid, name, baymodel_id, stack_id, '
|
||||||
|
'status, master_count, node_count, links, '
|
||||||
|
'cluster_create_timeout'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
def do_cluster_list(cs, args):
|
||||||
|
"""Print a list of available clusters."""
|
||||||
|
clusters = cs.clusters.list(marker=args.marker, limit=args.limit,
|
||||||
|
sort_key=args.sort_key,
|
||||||
|
sort_dir=args.sort_dir)
|
||||||
|
columns = ['uuid', 'name', 'node_count', 'master_count', 'status']
|
||||||
|
columns += utils._get_list_table_columns_and_formatters(
|
||||||
|
args.fields, clusters,
|
||||||
|
exclude_fields=(c.lower() for c in columns))[0]
|
||||||
|
utils.print_list(clusters, columns,
|
||||||
|
{'versions': magnum_utils.print_list_field('versions')},
|
||||||
|
sortby_index=None)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('--name',
|
||||||
|
metavar='<name>',
|
||||||
|
help='Name of the cluster to create.')
|
||||||
|
@utils.arg('--cluster-template',
|
||||||
|
required=True,
|
||||||
|
metavar='<cluster_template>',
|
||||||
|
help='ID or name of the cluster template.')
|
||||||
|
@utils.arg('--node-count',
|
||||||
|
metavar='<node-count>',
|
||||||
|
type=int,
|
||||||
|
default=1,
|
||||||
|
help='The cluster node count.')
|
||||||
|
@utils.arg('--master-count',
|
||||||
|
metavar='<master-count>',
|
||||||
|
type=int,
|
||||||
|
default=1,
|
||||||
|
help='The number of master nodes for the cluster.')
|
||||||
|
@utils.arg('--discovery-url',
|
||||||
|
metavar='<discovery-url>',
|
||||||
|
help='Specifies custom discovery url for node discovery.')
|
||||||
|
@utils.arg('--timeout',
|
||||||
|
metavar='<timeout>',
|
||||||
|
type=int,
|
||||||
|
default=60,
|
||||||
|
help='The timeout for cluster creation in minutes. The default '
|
||||||
|
'is 60 minutes.')
|
||||||
|
def do_cluster_create(cs, args):
|
||||||
|
"""Create a cluster."""
|
||||||
|
cluster_template = cs.cluster_templates.get(args.cluster_template)
|
||||||
|
|
||||||
|
opts = {}
|
||||||
|
opts['name'] = args.name
|
||||||
|
opts['cluster_template_id'] = cluster_template.uuid
|
||||||
|
opts['node_count'] = args.node_count
|
||||||
|
opts['master_count'] = args.master_count
|
||||||
|
opts['discovery_url'] = args.discovery_url
|
||||||
|
opts['create_timeout'] = args.timeout
|
||||||
|
try:
|
||||||
|
cluster = cs.clusters.create(**opts)
|
||||||
|
_show_cluster(cluster)
|
||||||
|
except Exception as e:
|
||||||
|
print("Create for cluster %s failed: %s" %
|
||||||
|
(opts['name'], e))
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('cluster',
|
||||||
|
metavar='<cluster>',
|
||||||
|
nargs='+',
|
||||||
|
help='ID or name of the (cluster)s to delete.')
|
||||||
|
def do_cluster_delete(cs, args):
|
||||||
|
"""Delete specified cluster."""
|
||||||
|
for id in args.cluster:
|
||||||
|
try:
|
||||||
|
cs.clusters.delete(id)
|
||||||
|
print("Request to delete cluster %s has been accepted." %
|
||||||
|
id)
|
||||||
|
except Exception as e:
|
||||||
|
print("Delete for cluster %(cluster)s failed: %(e)s" %
|
||||||
|
{'cluster': id, 'e': e})
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('cluster',
|
||||||
|
metavar='<cluster>',
|
||||||
|
help='ID or name of the cluster to show.')
|
||||||
|
@utils.arg('--long',
|
||||||
|
action='store_true', default=False,
|
||||||
|
help='Display extra associated cluster template info.')
|
||||||
|
def do_cluster_show(cs, args):
|
||||||
|
"""Show details about the given cluster."""
|
||||||
|
cluster = cs.clusters.get(args.cluster)
|
||||||
|
if args.long:
|
||||||
|
cluster_template = \
|
||||||
|
cs.cluster_templates.get(cluster.cluster_template_id)
|
||||||
|
del cluster_template._info['links'], cluster_template._info['uuid']
|
||||||
|
|
||||||
|
for key in cluster_template._info:
|
||||||
|
if 'clustertemplate_' + key not in cluster._info:
|
||||||
|
cluster._info['clustertemplate_' + key] = \
|
||||||
|
cluster_template._info[key]
|
||||||
|
_show_cluster(cluster)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('cluster', metavar='<cluster>', help="UUID or name of cluster")
|
||||||
|
@utils.arg(
|
||||||
|
'op',
|
||||||
|
metavar='<op>',
|
||||||
|
choices=['add', 'replace', 'remove'],
|
||||||
|
help="Operations: 'add', 'replace' or 'remove'")
|
||||||
|
@utils.arg(
|
||||||
|
'attributes',
|
||||||
|
metavar='<path=value>',
|
||||||
|
nargs='+',
|
||||||
|
action='append',
|
||||||
|
default=[],
|
||||||
|
help="Attributes to add/replace or remove "
|
||||||
|
"(only PATH is necessary on remove)")
|
||||||
|
def do_cluster_update(cs, args):
|
||||||
|
"""Update information about the given cluster."""
|
||||||
|
patch = magnum_utils.args_array_to_patch(args.op, args.attributes[0])
|
||||||
|
cluster = cs.clusters.update(args.cluster, patch)
|
||||||
|
_show_cluster(cluster)
|
||||||
|
|
||||||
|
|
||||||
|
@utils.arg('cluster',
|
||||||
|
metavar='<cluster>',
|
||||||
|
help='ID or name of the cluster to retrieve config.')
|
||||||
|
@utils.arg('--dir',
|
||||||
|
metavar='<dir>',
|
||||||
|
default='.',
|
||||||
|
help='Directory to save the certificate and config files.')
|
||||||
|
@utils.arg('--force',
|
||||||
|
action='store_true', default=False,
|
||||||
|
help='Overwrite files if existing.')
|
||||||
|
def do_cluster_config(cs, args):
|
||||||
|
"""Configure native client to access cluster.
|
||||||
|
|
||||||
|
You can source the output of this command to get the native client of the
|
||||||
|
corresponding COE configured to access the cluster.
|
||||||
|
|
||||||
|
Example: eval $(magnum cluster-config <cluster-name>).
|
||||||
|
"""
|
||||||
|
cluster = cs.clusters.get(args.cluster)
|
||||||
|
if cluster.status not in ('CREATE_COMPLETE', 'UPDATE_COMPLETE'):
|
||||||
|
raise exceptions.CommandError("cluster in status %s" % cluster.status)
|
||||||
|
cluster_template = cs.cluster_templates.get(cluster.cluster_template_id)
|
||||||
|
opts = {
|
||||||
|
'cluster_uuid': cluster.uuid,
|
||||||
|
}
|
||||||
|
|
||||||
|
if not cluster_template.tls_disabled:
|
||||||
|
tls = _generate_csr_and_key()
|
||||||
|
tls['ca'] = cs.certificates.get(**opts).pem
|
||||||
|
opts['csr'] = tls['csr']
|
||||||
|
tls['cert'] = cs.certificates.create(**opts).pem
|
||||||
|
for k in ('key', 'cert', 'ca'):
|
||||||
|
fname = "%s/%s.pem" % (args.dir, k)
|
||||||
|
if os.path.exists(fname) and not args.force:
|
||||||
|
raise Exception("File %s exists, aborting." % fname)
|
||||||
|
else:
|
||||||
|
f = open(fname, "w")
|
||||||
|
f.write(tls[k])
|
||||||
|
f.close()
|
||||||
|
|
||||||
|
print(_config_cluster(cluster, cluster_template,
|
||||||
|
cfg_dir=args.dir, force=args.force))
|
||||||
|
|
||||||
|
|
||||||
|
def _config_cluster(cluster, cluster_template, cfg_dir='.', force=False):
|
||||||
|
"""Return and write configuration for the given cluster."""
|
||||||
|
if cluster_template.coe == 'kubernetes':
|
||||||
|
return _config_cluster_kubernetes(cluster, cluster_template,
|
||||||
|
cfg_dir, force)
|
||||||
|
elif cluster_template.coe == 'swarm':
|
||||||
|
return _config_cluster_swarm(cluster, cluster_template, cfg_dir, force)
|
||||||
|
|
||||||
|
|
||||||
|
def _config_cluster_kubernetes(cluster, cluster_template,
|
||||||
|
cfg_dir='.', force=False):
|
||||||
|
"""Return and write configuration for the given kubernetes cluster."""
|
||||||
|
cfg_file = "%s/config" % cfg_dir
|
||||||
|
if cluster_template.tls_disabled:
|
||||||
|
cfg = ("apiVersion: v1\n"
|
||||||
|
"clusters:\n"
|
||||||
|
"- cluster:\n"
|
||||||
|
" server: %(api_address)s\n"
|
||||||
|
" name: %(name)s\n"
|
||||||
|
"contexts:\n"
|
||||||
|
"- context:\n"
|
||||||
|
" cluster: %(name)s\n"
|
||||||
|
" user: %(name)s\n"
|
||||||
|
" name: default/%(name)s\n"
|
||||||
|
"current-context: default/%(name)s\n"
|
||||||
|
"kind: Config\n"
|
||||||
|
"preferences: {}\n"
|
||||||
|
"users:\n"
|
||||||
|
"- name: %(name)s'\n"
|
||||||
|
% {'name': cluster.name, 'api_address': cluster.api_address})
|
||||||
|
else:
|
||||||
|
cfg = ("apiVersion: v1\n"
|
||||||
|
"clusters:\n"
|
||||||
|
"- cluster:\n"
|
||||||
|
" certificate-authority: ca.pem\n"
|
||||||
|
" server: %(api_address)s\n"
|
||||||
|
" name: %(name)s\n"
|
||||||
|
"contexts:\n"
|
||||||
|
"- context:\n"
|
||||||
|
" cluster: %(name)s\n"
|
||||||
|
" user: %(name)s\n"
|
||||||
|
" name: default/%(name)s\n"
|
||||||
|
"current-context: default/%(name)s\n"
|
||||||
|
"kind: Config\n"
|
||||||
|
"preferences: {}\n"
|
||||||
|
"users:\n"
|
||||||
|
"- name: %(name)s\n"
|
||||||
|
" user:\n"
|
||||||
|
" client-certificate: cert.pem\n"
|
||||||
|
" client-key: key.pem\n"
|
||||||
|
% {'name': cluster.name, 'api_address': cluster.api_address})
|
||||||
|
|
||||||
|
if os.path.exists(cfg_file) and not force:
|
||||||
|
raise exceptions.CommandError("File %s exists, aborting." % cfg_file)
|
||||||
|
else:
|
||||||
|
f = open(cfg_file, "w")
|
||||||
|
f.write(cfg)
|
||||||
|
f.close()
|
||||||
|
if 'csh' in os.environ['SHELL']:
|
||||||
|
return "setenv KUBECONFIG %s\n" % cfg_file
|
||||||
|
else:
|
||||||
|
return "export KUBECONFIG=%s\n" % cfg_file
|
||||||
|
|
||||||
|
|
||||||
|
def _config_cluster_swarm(cluster, cluster_template, cfg_dir='.', force=False):
|
||||||
|
"""Return and write configuration for the given swarm cluster."""
|
||||||
|
if 'csh' in os.environ['SHELL']:
|
||||||
|
result = ("setenv DOCKER_HOST %(docker_host)s\n"
|
||||||
|
"setenv DOCKER_CERT_PATH %(cfg_dir)s\n"
|
||||||
|
"setenv DOCKER_TLS_VERIFY %(tls)s\n"
|
||||||
|
% {'docker_host': cluster.api_address,
|
||||||
|
'cfg_dir': cfg_dir,
|
||||||
|
'tls': not cluster_template.tls_disabled}
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
result = ("export DOCKER_HOST=%(docker_host)s\n"
|
||||||
|
"export DOCKER_CERT_PATH=%(cfg_dir)s\n"
|
||||||
|
"export DOCKER_TLS_VERIFY=%(tls)s\n"
|
||||||
|
% {'docker_host': cluster.api_address,
|
||||||
|
'cfg_dir': cfg_dir,
|
||||||
|
'tls': not cluster_template.tls_disabled}
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
|
def _generate_csr_and_key():
|
||||||
|
"""Return a dict with a new csr and key."""
|
||||||
|
key = rsa.generate_private_key(
|
||||||
|
public_exponent=65537,
|
||||||
|
key_size=2048,
|
||||||
|
backend=default_backend())
|
||||||
|
|
||||||
|
csr = x509.CertificateSigningRequestBuilder().subject_name(x509.Name([
|
||||||
|
x509.NameAttribute(NameOID.COMMON_NAME, u"Magnum User"),
|
||||||
|
])).sign(key, hashes.SHA256(), default_backend())
|
||||||
|
|
||||||
|
result = {
|
||||||
|
'csr': csr.public_bytes(encoding=serialization.Encoding.PEM),
|
||||||
|
'key': key.private_bytes(
|
||||||
|
encoding=serialization.Encoding.PEM,
|
||||||
|
format=serialization.PrivateFormat.TraditionalOpenSSL,
|
||||||
|
encryption_algorithm=serialization.NoEncryption()),
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
@ -16,11 +16,15 @@
|
|||||||
from magnumclient.v1 import baymodels_shell
|
from magnumclient.v1 import baymodels_shell
|
||||||
from magnumclient.v1 import bays_shell
|
from magnumclient.v1 import bays_shell
|
||||||
from magnumclient.v1 import certificates_shell
|
from magnumclient.v1 import certificates_shell
|
||||||
|
from magnumclient.v1 import cluster_templates_shell
|
||||||
|
from magnumclient.v1 import clusters_shell
|
||||||
from magnumclient.v1 import mservices_shell
|
from magnumclient.v1 import mservices_shell
|
||||||
|
|
||||||
COMMAND_MODULES = [
|
COMMAND_MODULES = [
|
||||||
baymodels_shell,
|
baymodels_shell,
|
||||||
bays_shell,
|
bays_shell,
|
||||||
certificates_shell,
|
certificates_shell,
|
||||||
|
clusters_shell,
|
||||||
|
cluster_templates_shell,
|
||||||
mservices_shell,
|
mservices_shell,
|
||||||
]
|
]
|
||||||
|
@ -14,4 +14,4 @@ oslo.utils>=3.16.0 # Apache-2.0
|
|||||||
os-client-config!=1.19.0,!=1.19.1,!=1.20.0,>=1.13.1 # Apache-2.0
|
os-client-config!=1.19.0,!=1.19.1,!=1.20.0,>=1.13.1 # Apache-2.0
|
||||||
PrettyTable<0.8,>=0.7 # BSD
|
PrettyTable<0.8,>=0.7 # BSD
|
||||||
cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
|
cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
|
||||||
|
decorator>=3.4.0 # BSD
|
||||||
|
Loading…
x
Reference in New Issue
Block a user