Add support for network groups
Network groups can be created and deleted with a new
action named network-group. Only attributes of the network group
are managed with these commands (i.e no support for managing IP
ranges attached to network groups).
Examples:
To create a new network group:
fuel network-group --create --node-group 1 --name "new network"
--release 2 --vlan 100 --cidr 10.0.0.0/24
To delete the specified network groups
fuel network-group --delete --network 1
fuel network-group --delete --network 2,3,4
To list all available network groups:
fuel network-group list
To filter network groups by node group:
fuel network-group --node-group 1
Change-Id: I63dce39d0e2308e4df1b07301257ae8c65c94a2b
Implements: blueprint templates-for-networking
This commit is contained in:
@@ -27,6 +27,7 @@ from fuelclient.cli.actions.interrupt import ResetAction
|
||||
from fuelclient.cli.actions.interrupt import StopAction
|
||||
from fuelclient.cli.actions.network import NetworkAction
|
||||
from fuelclient.cli.actions.network import NetworkTemplateAction
|
||||
from fuelclient.cli.actions.network_group import NetworkGroupAction
|
||||
from fuelclient.cli.actions.node import NodeAction
|
||||
from fuelclient.cli.actions.nodegroup import NodeGroupAction
|
||||
from fuelclient.cli.actions.notifications import NotificationsAction
|
||||
@@ -66,6 +67,7 @@ actions_tuple = (
|
||||
TokenAction,
|
||||
GraphAction,
|
||||
FuelVersionAction,
|
||||
NetworkGroupAction,
|
||||
)
|
||||
|
||||
actions = dict(
|
||||
|
||||
112
fuelclient/cli/actions/network_group.py
Normal file
112
fuelclient/cli/actions/network_group.py
Normal file
@@ -0,0 +1,112 @@
|
||||
# Copyright 2015 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.actions.base import Action
|
||||
from fuelclient.cli.actions.base import check_all
|
||||
import fuelclient.cli.arguments as Args
|
||||
from fuelclient.cli.arguments import group
|
||||
from fuelclient.cli.formatting import format_table
|
||||
from fuelclient.objects.network_group import NetworkGroup
|
||||
from fuelclient.objects.network_group import NetworkGroupCollection
|
||||
|
||||
|
||||
class NetworkGroupAction(Action):
|
||||
"""Show or modify network groups
|
||||
"""
|
||||
action_name = "network-group"
|
||||
acceptable_keys = ("id", "name", "vlan_start", "cidr",
|
||||
"gateway", "group_id")
|
||||
|
||||
def __init__(self):
|
||||
super(NetworkGroupAction, self).__init__()
|
||||
self.args = (
|
||||
Args.get_env_arg(),
|
||||
Args.get_name_arg("Name of new network group."),
|
||||
Args.get_node_group_arg("ID of node group."),
|
||||
Args.get_release_arg("Release ID this network group belongs to."),
|
||||
Args.get_vlan_arg("VLAN of network."),
|
||||
Args.get_cidr_arg("CIDR of network."),
|
||||
Args.get_gateway_arg("Gateway of network."),
|
||||
Args.get_network_group_arg("ID of network group."),
|
||||
Args.get_meta_arg("Metadata in JSON format to override default "
|
||||
"network metadata."),
|
||||
group(
|
||||
Args.get_create_network_arg(
|
||||
"Create a new network group for the specified "
|
||||
" node group."
|
||||
),
|
||||
Args.get_delete_arg("Delete specified network groups."),
|
||||
Args.get_list_arg("List all network groups.")
|
||||
)
|
||||
)
|
||||
self.flag_func_map = (
|
||||
("create", self.create),
|
||||
("delete", self.delete),
|
||||
(None, self.list)
|
||||
)
|
||||
|
||||
@check_all('nodegroup', 'name', 'cidr')
|
||||
def create(self, params):
|
||||
"""Create a new network group
|
||||
fuel network-group --create --node-group 1 --name "new network"
|
||||
--release 2 --vlan 100 --cidr 10.0.0.0/24
|
||||
"""
|
||||
NetworkGroup.create(
|
||||
params.name,
|
||||
params.release,
|
||||
params.vlan,
|
||||
params.cidr,
|
||||
params.gateway,
|
||||
int(params.nodegroup.pop()),
|
||||
params.meta
|
||||
)
|
||||
|
||||
self.serializer.print_to_output(
|
||||
{},
|
||||
"Network group {0} has been created".format(params.name)
|
||||
)
|
||||
|
||||
@check_all('network')
|
||||
def delete(self, params):
|
||||
"""Delete the specified network groups
|
||||
fuel network-group --delete --network 1
|
||||
fuel network-group --delete --network 2,3,4
|
||||
"""
|
||||
ngs = NetworkGroup.get_by_ids(params.network)
|
||||
for n in ngs:
|
||||
NetworkGroup.delete(n.id)
|
||||
|
||||
self.serializer.print_to_output(
|
||||
{},
|
||||
"Network groups with IDS {0} have been deleted.".format(
|
||||
','.join(params.network))
|
||||
)
|
||||
|
||||
def list(self, params):
|
||||
"""To list all available network groups:
|
||||
fuel network-group list
|
||||
|
||||
To filter them by node group:
|
||||
fuel network-group --node-group 1
|
||||
"""
|
||||
group_collection = NetworkGroupCollection.get_all()
|
||||
if params.nodegroup:
|
||||
group_collection.filter_by_group_id(int(params.nodegroup.pop()))
|
||||
self.serializer.print_to_output(
|
||||
group_collection.data,
|
||||
format_table(
|
||||
group_collection.data,
|
||||
acceptable_keys=self.acceptable_keys,
|
||||
)
|
||||
)
|
||||
@@ -439,6 +439,38 @@ def get_group_arg(help_msg):
|
||||
return get_set_type_arg("group", help=help_msg)
|
||||
|
||||
|
||||
def get_node_group_arg(help_msg):
|
||||
return get_set_type_arg("nodegroup", flags=("--node-group",),
|
||||
help=help_msg)
|
||||
|
||||
|
||||
def get_vlan_arg(help_msg):
|
||||
return get_int_arg("vlan", help=help_msg)
|
||||
|
||||
|
||||
def get_cidr_arg(help_msg):
|
||||
return get_str_arg("cidr", help=help_msg)
|
||||
|
||||
|
||||
def get_gateway_arg(help_msg):
|
||||
return get_str_arg("gateway", help=help_msg)
|
||||
|
||||
|
||||
def get_meta_arg(help_msg):
|
||||
return get_str_arg("meta", help=help_msg)
|
||||
|
||||
|
||||
def get_create_network_arg(help_msg):
|
||||
return get_boolean_arg(
|
||||
"create",
|
||||
flags=("-c", "--create"),
|
||||
help=help_msg)
|
||||
|
||||
|
||||
def get_network_group_arg(help_msg):
|
||||
return get_set_type_arg("network", help=help_msg)
|
||||
|
||||
|
||||
def get_release_arg(help_msg, required=False):
|
||||
return get_int_arg(
|
||||
"release",
|
||||
|
||||
100
fuelclient/objects/network_group.py
Normal file
100
fuelclient/objects/network_group.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# Copyright 2014 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 operator import attrgetter
|
||||
|
||||
from fuelclient.objects.base import BaseObject
|
||||
|
||||
|
||||
class NetworkGroup(BaseObject):
|
||||
|
||||
class_api_path = "networks/"
|
||||
instance_api_path = "networks/{0}/"
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.get_fresh_data()["name"]
|
||||
|
||||
@classmethod
|
||||
def create(cls, name, release, vlan, cidr, gateway,
|
||||
group_id, meta=None):
|
||||
|
||||
metadata = {
|
||||
'notation': 'ip_ranges',
|
||||
'render_type': None,
|
||||
'map_priority': 2,
|
||||
'configurable': True,
|
||||
'use_gateway': False,
|
||||
'name': name,
|
||||
'cidr': cidr,
|
||||
'vlan_start': vlan
|
||||
}
|
||||
if meta:
|
||||
metadata.update(meta)
|
||||
|
||||
network_group = {
|
||||
'name': name,
|
||||
'release': release,
|
||||
'vlan_start': vlan,
|
||||
'cidr': cidr,
|
||||
'gateway': gateway,
|
||||
'meta': metadata,
|
||||
'group_id': group_id,
|
||||
}
|
||||
|
||||
return cls.connection.post_request(
|
||||
cls.class_api_path,
|
||||
network_group,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def delete(cls, network_id):
|
||||
return cls.connection.delete_request(
|
||||
cls.instance_api_path.format(network_id)
|
||||
)
|
||||
|
||||
|
||||
class NetworkGroupCollection(object):
|
||||
|
||||
def __init__(self, networks):
|
||||
self.collection = networks
|
||||
|
||||
@classmethod
|
||||
def init_with_ids(cls, ids):
|
||||
return cls(map(NetworkGroup, ids))
|
||||
|
||||
@classmethod
|
||||
def init_with_data(cls, data):
|
||||
return cls(map(NetworkGroup.init_with_data, data))
|
||||
|
||||
def __str__(self):
|
||||
return "{0} [{1}]".format(
|
||||
self.__class__.__name__,
|
||||
", ".join(map(lambda n: str(n.id), self.collection))
|
||||
)
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.collection)
|
||||
|
||||
@property
|
||||
def data(self):
|
||||
return map(attrgetter("data"), self.collection)
|
||||
|
||||
@classmethod
|
||||
def get_all(cls):
|
||||
return cls(NetworkGroup.get_all())
|
||||
|
||||
def filter_by_group_id(self, group_id):
|
||||
self.collection = filter(lambda net: net.data['group_id'] == group_id,
|
||||
self.collection)
|
||||
@@ -239,6 +239,30 @@ class TestHandlers(base.BaseTestCase):
|
||||
msg
|
||||
)
|
||||
|
||||
def test_create_network_group_fails_w_duplicate_name(self):
|
||||
err_msg = ("(Network with name storage already exists "
|
||||
"in node group default)\n")
|
||||
|
||||
self.run_cli_commands((
|
||||
"env create --name=NewEnv --release=1 --nst=gre",
|
||||
))
|
||||
self.check_for_stderr(
|
||||
("network-group --create --name storage --node-group 1 "
|
||||
"--vlan 10 --cidr 10.0.0.0/24"),
|
||||
err_msg,
|
||||
check_errors=False
|
||||
)
|
||||
|
||||
def test_create_network_group_fails_w_invalid_group(self):
|
||||
err_msg = "(Node group with ID 0 does not exist)\n"
|
||||
|
||||
self.check_for_stderr(
|
||||
("network-group --create --name test --node-group 0 "
|
||||
"--vlan 10 --cidr 10.0.0.0/24"),
|
||||
err_msg,
|
||||
check_errors=False
|
||||
)
|
||||
|
||||
|
||||
class TestUserActions(base.BaseTestCase):
|
||||
|
||||
|
||||
100
fuelclient/tests/v1/unit/test_network_groups.py
Normal file
100
fuelclient/tests/v1/unit/test_network_groups.py
Normal file
@@ -0,0 +1,100 @@
|
||||
# Copyright 2015 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 requests_mock
|
||||
|
||||
from fuelclient.tests import base
|
||||
|
||||
|
||||
@requests_mock.mock()
|
||||
class TestNetworkGroupActions(base.UnitTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(TestNetworkGroupActions, self).setUp()
|
||||
|
||||
self.env_id = 42
|
||||
self.req_base_path = '/api/v1/networks/'
|
||||
|
||||
def test_list_network_groups(self, mreq):
|
||||
mreq.get(self.req_base_path)
|
||||
list_commands = [
|
||||
['fuel', 'network-group', '--list'], ['fuel', 'network-group']]
|
||||
|
||||
for cmd in list_commands:
|
||||
self.execute(cmd)
|
||||
self.assertEqual(mreq.last_request.method, 'GET')
|
||||
self.assertEqual(mreq.last_request.path, self.req_base_path)
|
||||
|
||||
def test_list_network_groups_filtering(self, mreq):
|
||||
mreq.get(self.req_base_path)
|
||||
|
||||
self.execute(['fuel', 'network-group', '--node-group', '1'])
|
||||
|
||||
self.assertEqual(mreq.last_request.method, 'GET')
|
||||
self.assertEqual(mreq.last_request.path, self.req_base_path)
|
||||
|
||||
def test_create_network_group(self, mreq):
|
||||
mreq.post(self.req_base_path)
|
||||
self.execute(['fuel', 'network-group', '--create', '--cidr',
|
||||
'10.0.0.0/24', '--name', 'test network',
|
||||
'--node-group', '1'])
|
||||
|
||||
call_data = mreq.last_request.json()
|
||||
self.assertEqual(1, call_data['group_id'])
|
||||
self.assertEqual("test network", call_data['name'])
|
||||
|
||||
self.assertEqual(mreq.last_request.method, 'POST')
|
||||
self.assertEqual(mreq.last_request.path, self.req_base_path)
|
||||
|
||||
def test_create_network_group_required_args(self, mreq):
|
||||
with mock.patch("sys.stderr") as m_stderr:
|
||||
with self.assertRaises(SystemExit):
|
||||
self.execute(
|
||||
['fuel', 'network-group', '--create'])
|
||||
|
||||
self.assertIn('--nodegroup", "--name" and "--cidr" required!',
|
||||
m_stderr.write.call_args[0][0])
|
||||
|
||||
def test_delete_network_group_required_args(self, mreq):
|
||||
with mock.patch("sys.stderr") as m_stderr:
|
||||
with self.assertRaises(SystemExit):
|
||||
self.execute(
|
||||
['fuel', 'network-group', '--delete'])
|
||||
|
||||
self.assertIn('"--network" required!', m_stderr.write.call_args[0][0])
|
||||
|
||||
def test_delete_network_group(self, mreq):
|
||||
path = self.req_base_path + str(self.env_id) + '/'
|
||||
mreq.delete(path)
|
||||
self.execute(
|
||||
['fuel', 'network-group', '--delete',
|
||||
'--network', str(self.env_id)])
|
||||
|
||||
self.assertEqual(mreq.last_request.method, 'DELETE')
|
||||
self.assertEqual(mreq.last_request.path, path)
|
||||
|
||||
def test_network_group_duplicate_name(self, mreq):
|
||||
mreq.post(self.req_base_path, status_code=409)
|
||||
|
||||
with mock.patch("sys.stderr") as m_stderr:
|
||||
with self.assertRaises(SystemExit):
|
||||
self.execute(
|
||||
['fuel', 'network-group', '--create', '--cidr',
|
||||
'10.0.0.0/24', '--name', 'test network', '--node-group',
|
||||
'1'])
|
||||
|
||||
self.assertIn("409 Client Error", m_stderr.write.call_args[0][0])
|
||||
self.assertEqual(mreq.last_request.method, 'POST')
|
||||
self.assertEqual(mreq.last_request.path, self.req_base_path)
|
||||
Reference in New Issue
Block a user