Merge "Adds 'cluster' and 'cluster template'"
This commit is contained in:
commit
850f483523
@ -24,6 +24,7 @@ import os
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
import decorator
|
||||
from magnumclient.common.apiclient import exceptions
|
||||
from oslo_utils import encodeutils
|
||||
from oslo_utils import strutils
|
||||
@ -75,6 +76,22 @@ def validate_args(fn, *args, **kwargs):
|
||||
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):
|
||||
"""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
|
||||
# under the License.
|
||||
|
||||
from magnumclient.common import base
|
||||
from magnumclient.common import utils
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.v1 import basemodels
|
||||
|
||||
|
||||
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']
|
||||
CREATION_ATTRIBUTES = basemodels.CREATION_ATTRIBUTES
|
||||
|
||||
|
||||
class BayModel(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<BayModel %s>" % self._info
|
||||
class BayModel(basemodels.BaseModel):
|
||||
model_name = "BayModel"
|
||||
|
||||
|
||||
class BayModelManager(base.Manager):
|
||||
class BayModelManager(basemodels.BaseModelManager):
|
||||
api_name = "baymodels"
|
||||
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 _
|
||||
|
||||
|
||||
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):
|
||||
del baymodel._info['links']
|
||||
utils.print_dict(baymodel._info)
|
||||
@ -115,6 +120,7 @@ def _show_baymodel(baymodel):
|
||||
action='store_true', default=False,
|
||||
help='Indicates whether created bays should have a load balancer '
|
||||
'for master nodes or not.')
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_baymodel_create(cs, args):
|
||||
"""Create a baymodel."""
|
||||
opts = {}
|
||||
@ -150,6 +156,7 @@ def do_baymodel_create(cs, args):
|
||||
metavar='<baymodels>',
|
||||
nargs='+',
|
||||
help='ID or name of the (baymodel)s to delete.')
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_baymodel_delete(cs, args):
|
||||
"""Delete specified baymodel."""
|
||||
for baymodel in args.baymodels:
|
||||
@ -165,6 +172,7 @@ def do_baymodel_delete(cs, args):
|
||||
@utils.arg('baymodel',
|
||||
metavar='<baymodel>',
|
||||
help='ID or name of the baymodel to show.')
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_baymodel_show(cs, args):
|
||||
"""Show details about the given 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'
|
||||
)
|
||||
)
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_baymodel_list(cs, args):
|
||||
"""Print a list of baymodels."""
|
||||
nodes = cs.baymodels.list(limit=args.limit,
|
||||
@ -218,6 +227,7 @@ def do_baymodel_list(cs, args):
|
||||
default=[],
|
||||
help="Attributes to add/replace or remove "
|
||||
"(only PATH is necessary on remove)")
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_baymodel_update(cs, args):
|
||||
"""Updates one or more baymodel attributes."""
|
||||
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
|
||||
# under the License.
|
||||
|
||||
from magnumclient.common import base
|
||||
from magnumclient.common import utils
|
||||
from magnumclient import exceptions
|
||||
from magnumclient.v1 import baseunit
|
||||
|
||||
|
||||
CREATION_ATTRIBUTES = ['name', 'baymodel_id', 'node_count', 'discovery_url',
|
||||
'bay_create_timeout', 'master_count']
|
||||
CREATION_ATTRIBUTES = baseunit.CREATION_ATTRIBUTES
|
||||
CREATION_ATTRIBUTES.append('baymodel_id')
|
||||
CREATION_ATTRIBUTES.append('bay_create_timeout')
|
||||
|
||||
|
||||
class Bay(base.Resource):
|
||||
def __repr__(self):
|
||||
return "<Bay %s>" % self._info
|
||||
class Bay(baseunit.BaseTemplate):
|
||||
template_name = "Bays"
|
||||
|
||||
|
||||
class BayManager(base.Manager):
|
||||
class BayManager(baseunit.BaseTemplateManager):
|
||||
resource_class = Bay
|
||||
|
||||
@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)
|
||||
template_name = 'bays'
|
||||
|
@ -26,6 +26,11 @@ from cryptography.x509.oid import NameOID
|
||||
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):
|
||||
del bay._info['links']
|
||||
utils.print_dict(bay._info)
|
||||
@ -55,6 +60,7 @@ def _show_bay(bay):
|
||||
'status, master_count, node_count, links, bay_create_timeout'
|
||||
)
|
||||
)
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_bay_list(cs, args):
|
||||
"""Print a list of available bays."""
|
||||
bays = cs.bays.list(marker=args.marker, limit=args.limit,
|
||||
@ -69,6 +75,7 @@ def do_bay_list(cs, args):
|
||||
sortby_index=None)
|
||||
|
||||
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
@utils.arg('--name',
|
||||
metavar='<name>',
|
||||
help='Name of the bay to create.')
|
||||
@ -118,6 +125,7 @@ def do_bay_create(cs, args):
|
||||
metavar='<bay>',
|
||||
nargs='+',
|
||||
help='ID or name of the (bay)s to delete.')
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_bay_delete(cs, args):
|
||||
"""Delete specified bay."""
|
||||
for id in args.bay:
|
||||
@ -136,6 +144,7 @@ def do_bay_delete(cs, args):
|
||||
@utils.arg('--long',
|
||||
action='store_true', default=False,
|
||||
help='Display extra associated Baymodel info.')
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_bay_show(cs, args):
|
||||
"""Show details about the given bay."""
|
||||
bay = cs.bays.get(args.bay)
|
||||
@ -163,6 +172,7 @@ def do_bay_show(cs, args):
|
||||
default=[],
|
||||
help="Attributes to add/replace or remove "
|
||||
"(only PATH is necessary on remove)")
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_bay_update(cs, args):
|
||||
"""Update information about the given bay."""
|
||||
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',
|
||||
action='store_true', default=False,
|
||||
help='Overwrite files if existing.')
|
||||
@utils.deprecated(DEPRECATION_MESSAGE)
|
||||
def do_bay_config(cs, args):
|
||||
"""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
|
||||
if baymodel.tls_disabled:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
"bays:\n"
|
||||
"- bay:\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" bay: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: 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})
|
||||
else:
|
||||
cfg = ("apiVersion: v1\n"
|
||||
"clusters:\n"
|
||||
"- cluster:\n"
|
||||
"bays:\n"
|
||||
"- bay:\n"
|
||||
" certificate-authority: ca.pem\n"
|
||||
" server: %(api_address)s\n"
|
||||
" name: %(name)s\n"
|
||||
"contexts:\n"
|
||||
"- context:\n"
|
||||
" cluster: %(name)s\n"
|
||||
" bay: %(name)s\n"
|
||||
" user: %(name)s\n"
|
||||
" name: 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 bays
|
||||
from magnumclient.v1 import certificates
|
||||
from magnumclient.v1 import cluster_templates
|
||||
from magnumclient.v1 import clusters
|
||||
from magnumclient.v1 import mservices
|
||||
|
||||
|
||||
@ -145,6 +147,9 @@ class Client(object):
|
||||
endpoint_override=endpoint_override,
|
||||
)
|
||||
self.bays = bays.BayManager(self.http_client)
|
||||
self.clusters = clusters.ClusterManager(self.http_client)
|
||||
self.certificates = certificates.CertificateManager(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)
|
||||
|
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 bays_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
|
||||
|
||||
COMMAND_MODULES = [
|
||||
baymodels_shell,
|
||||
bays_shell,
|
||||
certificates_shell,
|
||||
clusters_shell,
|
||||
cluster_templates_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
|
||||
PrettyTable<0.8,>=0.7 # BSD
|
||||
cryptography!=1.3.0,>=1.0 # BSD/Apache-2.0
|
||||
|
||||
decorator>=3.4.0 # BSD
|
||||
|
Loading…
Reference in New Issue
Block a user