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:
Ryan Moe
2015-07-21 17:30:30 -07:00
committed by Ivan Kliuk
parent 58c411d87a
commit c2f8b78686
6 changed files with 370 additions and 0 deletions

View File

@@ -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(

View 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,
)
)

View File

@@ -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",

View 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)

View File

@@ -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):

View 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)