Basic VIP management commands added to Nailgun CLI v2

Now Naigun CLI v2 supports VIP configuration management commands
that allow to download and upload VIP configuration:

fuel2 vip download --env 1 --ip-address-id 1 --file firstvip.yaml
fuel2 vip upload --env 1 --file ip_address.yaml

Partial-Bug: #1482399
Implements Blueprint: allow-any-vip

Change-Id: Ib9b15bfdb7d4514919efbac2dab4416d088aa1e9
This commit is contained in:
Ilya Kutukov
2016-02-09 03:39:40 +03:00
parent 7d6f471a9e
commit c45bd4ebb9
8 changed files with 322 additions and 2 deletions

View File

@@ -55,6 +55,7 @@ def get_client(resource, version='v1'):
'openstack-config': v1.openstack_config,
'plugins': v1.plugins,
'task': v1.task,
'vip': v1.vip
}
}

View File

@@ -59,6 +59,7 @@ class VIPAction(Action):
fuel --env 1 vip --download --file vip.yaml
where --file param is optional
"""
env = Environment(params.env)
vips_data = env.get_vips_data(
ip_address_id=getattr(params, 'ip-address-id'),

126
fuelclient/commands/vip.py Normal file
View File

@@ -0,0 +1,126 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from fuelclient.commands import base
class VipMixIn(object):
entity_name = 'vip'
@staticmethod
def add_env_id_arg(parser):
parser.add_argument(
'-e',
'--env',
type=int,
required=True,
help='Environment identifier'
)
class VipDownload(VipMixIn, base.BaseCommand):
"""Download VIPs configuration."""
@staticmethod
def add_ip_address_id_arg(parser):
parser.add_argument(
"-a",
"--ip-address-id",
type=int,
default=None,
required=False,
help="IP address entity identifier"
)
@staticmethod
def add_network_id_arg(parser):
parser.add_argument(
"-n",
"--network",
type=int,
default=None,
required=False,
help="Network identifier"
)
@staticmethod
def add_network_role_arg(parser):
parser.add_argument(
"-r",
"--network-role",
type=str,
default=None,
required=False,
help="Network role string"
)
@staticmethod
def add_file_arg(parser):
parser.add_argument(
'-f',
'--file',
type=str,
required=False,
default=None,
help='YAML file that contains openstack configuration.'
)
def get_parser(self, prog_name):
parser = super(VipDownload, self).get_parser(prog_name)
self.add_env_id_arg(parser)
self.add_ip_address_id_arg(parser)
self.add_file_arg(parser)
self.add_network_id_arg(parser)
self.add_network_role_arg(parser)
return parser
def take_action(self, args):
vips_data_file_path = self.client.download(
env_id=args.env,
ip_addr_id=args.ip_address_id,
network_id=args.network,
network_role=args.network_role,
file_path=args.file
)
self.app.stdout.write(
"VIP configuration for environment with id={0}"
" downloaded to {1}".format(args.env, vips_data_file_path)
)
class VipUpload(VipMixIn, base.BaseCommand):
"""Upload new VIPs configuration from file."""
@staticmethod
def add_file_arg(parser):
parser.add_argument(
'-f',
'--file',
required=True,
type=str,
help='YAML file that contains openstack configuration.'
)
def get_parser(self, prog_name):
parser = super(VipUpload, self).get_parser(prog_name)
self.add_env_id_arg(parser)
self.add_file_arg(parser)
return parser
def take_action(self, args):
self.client.upload(env_id=args.env, file_path=args.file)
self.app.stdout.write("VIP configuration uploaded.")

View File

@@ -0,0 +1,68 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from fuelclient.tests.unit.v2.cli import test_engine
class TestVIPActions(test_engine.BaseCLITest):
def _test_cmd(self, method, cmd_line, expected_kwargs):
self.m_get_client.reset_mock()
self.m_client.get_filtered.reset_mock()
self.m_client.__getattr__(method).return_value = 'vips_1.yaml'
self.exec_command('vip {0} {1}'.format(method, cmd_line))
self.m_get_client.assert_called_once_with('vip', mock.ANY)
self.m_client.__getattr__(method).assert_called_once_with(
**expected_kwargs)
def test_vip_download(self):
self._test_cmd('download', '--env 1', dict(
env_id=1,
file_path=None,
ip_addr_id=None,
network_id=None,
network_role=None))
def test_vip_download_with_network_id(self):
self._test_cmd('download', '--env 1 --network 3', dict(
env_id=1,
file_path=None,
ip_addr_id=None,
network_id=3,
network_role=None))
def test_vip_download_with_network_role(self):
self._test_cmd('download', '--env 1 --network-role some/role', dict(
env_id=1,
file_path=None,
ip_addr_id=None,
network_id=None,
network_role='some/role'))
def test_single_vip_download(self):
self._test_cmd('download', '--env 1 --ip-address-id 5', dict(
env_id=1,
file_path=None,
ip_addr_id=5,
network_id=None,
network_role=None))
def test_vip_upload(self):
self._test_cmd('upload', '--env 1 --file vips_1.yaml', dict(
env_id=1,
file_path='vips_1.yaml'))

View File

@@ -0,0 +1,66 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
import yaml
import fuelclient
from fuelclient.tests.unit.v1.test_vip_action import MANY_VIPS_YAML
from fuelclient.tests.unit.v2.lib import test_api
class TestVipFacade(test_api.BaseLibTest):
def setUp(self):
super(TestVipFacade, self).setUp()
self.version = 'v1'
self.env_id = 42
self.res_uri = (
'/api/{version}/clusters/{env_id}'
'/network_configuration/ips/vips/'.format(
version=self.version, env_id=self.env_id))
self.client = fuelclient.get_client('vip', self.version)
def test_vip_upload(self):
expected_body = yaml.load(MANY_VIPS_YAML)
matcher = self.m_request.put(self.res_uri, json=expected_body)
m_open = mock.mock_open(read_data=MANY_VIPS_YAML)
with mock.patch('fuelclient.cli.serializers.open',
m_open, create=True):
with mock.patch('fuelclient.objects.environment.os') as env_os:
env_os.path.exists.return_value = True
self.client.upload(self.env_id, 'vips_1.yaml')
self.assertTrue(matcher.called)
self.assertEqual(expected_body, matcher.last_request.json())
def test_vip_download(self):
expected_body = yaml.load(MANY_VIPS_YAML)
matcher = self.m_request.get(self.res_uri, json=expected_body)
m_open = mock.mock_open()
with mock.patch('fuelclient.cli.serializers.open',
m_open, create=True):
self.client.download(self.env_id)
self.assertTrue(matcher.called)
written_yaml = yaml.safe_load(m_open().write.mock_calls[0][1][0])
expected_yaml = yaml.safe_load(MANY_VIPS_YAML)
self.assertEqual(written_yaml, expected_yaml)

View File

@@ -17,8 +17,9 @@ from fuelclient.v1 import fuelversion
from fuelclient.v1 import network_group
from fuelclient.v1 import node
from fuelclient.v1 import openstack_config
from fuelclient.v1 import task
from fuelclient.v1 import plugins
from fuelclient.v1 import task
from fuelclient.v1 import vip
# Please keeps the list in alphabetical order
__all__ = ('environment',
@@ -27,4 +28,5 @@ __all__ = ('environment',
'node',
'openstack_config',
'plugins',
'task',)
'task',
'vip')

54
fuelclient/v1/vip.py Normal file
View File

@@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from fuelclient.cli.serializers import Serializer
from fuelclient import objects
from fuelclient.v1 import base_v1
class VipClient(base_v1.BaseV1Client):
_entity_wrapper = objects.Environment
@staticmethod
def download(env_id, ip_addr_id=None, network_id=None,
network_role=None, file_path=None):
env = objects.Environment(env_id)
vips_data = env.get_vips_data(
ip_address_id=ip_addr_id,
network=network_id,
network_role=network_role
)
vips_data_file_path = env.write_vips_data_to_file(
vips_data,
file_path=file_path,
serializer=Serializer()
)
return vips_data_file_path
@staticmethod
def upload(env_id, file_path):
env = objects.Environment(env_id)
vips_data = env.read_vips_data_from_file(
file_path=file_path,
serializer=Serializer()
)
env.set_vips_data(vips_data)
def get_client():
return VipClient()

View File

@@ -62,6 +62,8 @@ fuelclient =
openstack-config_upload=fuelclient.commands.openstack_config:OpenstackConfigUpload
openstack-config_download=fuelclient.commands.openstack_config:OpenstackConfigDownload
openstack-config_execute=fuelclient.commands.openstack_config:OpenstackConfigExecute
vip_upload=fuelclient.commands.vip:VipUpload
vip_download=fuelclient.commands.vip:VipDownload
[global]
setup-hooks =