Commit 4c4c0f7c authored by Francis Dupont's avatar Francis Dupont Committed by Tomek Mrugalski

[65-libyang-shared-network-translator] Added shared network translator code and tests from kea-yang

parent 180a6382
......@@ -25,6 +25,8 @@ libkea_yang_la_SOURCES += translator_pool.cc translator_pool.h
libkea_yang_la_SOURCES += translator_pd_pool.cc translator_pd_pool.h
libkea_yang_la_SOURCES += translator_host.cc translator_host.h
libkea_yang_la_SOURCES += translator_subnet.cc translator_subnet.h
libkea_yang_la_SOURCES += translator_shared_network.cc
libkea_yang_la_SOURCES += translator_shared_network.h
libkea_yang_la_SOURCES += yang_models.h
libkea_yang_la_LIBADD = $(top_builddir)/src/lib/asiolink/libkea-asiolink.la
......@@ -56,6 +58,7 @@ libkea_yang_include_HEADERS = \
translator_option_def.h \
translator_pool.h \
translator_pd_pool.h \
translator_shared_network.h \
translator_subnet.h \
yang_models.h
......
......@@ -34,6 +34,7 @@ run_unittests_SOURCES += translator_pool_unittests.cc
run_unittests_SOURCES += translator_pd_pool_unittests.cc
run_unittests_SOURCES += translator_host_unittests.cc
run_unittests_SOURCES += translator_subnet_unittests.cc
run_unittests_SOURCES += translator_shared_network_unittests.cc
run_unittests_SOURCES += run_unittests.cc
run_unittests_CPPFLAGS = $(AM_CPPFLAGS) $(GTEST_INCLUDES)
run_unittests_LDFLAGS = $(AM_LDFLAGS) $(GTEST_LDFLAGS)
......
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <config.h>
#include <yang/translator_shared_network.h>
#include <yang/yang_models.h>
#include <yang/tests/sysrepo_setup.h>
#include <gtest/gtest.h>
using namespace std;
using namespace isc;
using namespace isc::data;
using namespace isc::yang;
using namespace isc::yang::test;
namespace {
/// @brief Translator name.
extern char const shared_networks[] = "shared networks";
/// @brief Test fixture class for @ref TranslatorSharedNetworks.
class TranslatorSharedNetworksTest :
public GenericTranslatorTest<shared_networks, TranslatorSharedNetworks> {
public:
/// Constructor.
TranslatorSharedNetworksTest() { }
/// Destructor (does nothing).
virtual ~TranslatorSharedNetworksTest() { }
};
// This test verifies that an empty shared network list can be properly
// translated from YANG to JSON.
TEST_F(TranslatorSharedNetworksTest, getEmpty) {
useModel(KEA_DHCP4_SERVER);
// Get the shared network list and check if it is empty.
const string& xpath = "/kea-dhcp4-server:config/shared-networks";
ConstElementPtr networks;
EXPECT_NO_THROW(networks = t_obj_->getSharedNetworks(xpath));
ASSERT_TRUE(networks);
ASSERT_EQ(Element::list, networks->getType());
EXPECT_EQ(0, networks->size());
}
// This test verifies that one shared network can be properly
// translated from YANG to JSON.
TEST_F(TranslatorSharedNetworksTest, get) {
useModel(KEA_DHCP6_SERVER);
// Create the subnet 2001:db8::/48 #111 in shared network foo.
const string& xpath = "/kea-dhcp6-server:config/shared-networks";
const string& xnetwork = xpath + "/shared-network[name='foo']";
const string& xsubnet = xnetwork + "/subnet6/subnet6[id='111']/subnet";
S_Val v_subnet(new Val("2001:db8::/48", SR_STRING_T));
EXPECT_NO_THROW(sess_->set_item(xsubnet.c_str(), v_subnet));
// Get the shared network.
ConstElementPtr network;
EXPECT_NO_THROW(network = t_obj_->getSharedNetwork(xnetwork));
ASSERT_TRUE(network);
ElementPtr subnet = Element::createMap();
subnet->set("id", Element::create(111));
subnet->set("subnet", Element::create(string("2001:db8::/48")));
ElementPtr subnets = Element::createList();
subnets->add(subnet);
ElementPtr expected = Element::createMap();
expected->set("name", Element::create(string("foo")));
expected->set("subnet6", subnets);
EXPECT_TRUE(expected->equals(*network));
// Get the shared network list and check if the shared network is in it.
ConstElementPtr networks;
EXPECT_NO_THROW(networks = t_obj_->getSharedNetworks(xpath));
ASSERT_TRUE(networks);
ASSERT_EQ(Element::list, networks->getType());
ASSERT_EQ(1, networks->size());
EXPECT_TRUE(network->equals(*networks->get(0)));
}
// This test verifies that an empty shared network list can be properly
// translated from JSON to YANG.
TEST_F(TranslatorSharedNetworksTest, setEmpty) {
useModel(KEA_DHCP4_SERVER);
// Set empty list.
const string& xpath = "/kea-dhcp4-server:config/shared-networks";
ConstElementPtr networks = Element::createList();
EXPECT_NO_THROW(t_obj_->setSharedNetworks(xpath, networks));
// Get it back.
networks.reset();
EXPECT_NO_THROW(networks = t_obj_->getSharedNetworks(xpath));
ASSERT_TRUE(networks);
ASSERT_EQ(Element::list, networks->getType());
EXPECT_EQ(0, networks->size());
// Check that the tree representation is empty.
S_Tree tree;
EXPECT_NO_THROW(tree = sess_->get_subtree("/kea-dhcp4-server:config"));
EXPECT_FALSE(tree);
}
// This test verifies that one shared network can be properly
// translated from JSON to YANG.
TEST_F(TranslatorSharedNetworksTest, set) {
useModel(KEA_DHCP6_SERVER);
// Set one shared network.
const string& xpath = "/kea-dhcp6-server:config/shared-networks";
ElementPtr networks = Element::createList();
ElementPtr share = Element::createMap();
ElementPtr subnets = Element::createList();
ElementPtr subnet = Element::createMap();
subnet->set("subnet", Element::create(string("2001:db8::/48")));
subnet->set("id", Element::create(123));
subnets->add(subnet);
share->set("name", Element::create(string("foo")));
share->set("subnet6", subnets);
networks->add(share);
EXPECT_NO_THROW(t_obj_->setSharedNetworks(xpath, networks));
// Get it back.
networks.reset();
EXPECT_NO_THROW(networks = t_obj_->getSharedNetworks(xpath));
ASSERT_TRUE(networks);
ASSERT_EQ(Element::list, networks->getType());
ASSERT_EQ(1, networks->size());
EXPECT_TRUE(share->equals(*networks->get(0)));
// Check the tree representation.
S_Tree tree;
EXPECT_NO_THROW(tree = sess_->get_subtree("/kea-dhcp6-server:config"));
ASSERT_TRUE(tree);
string expected =
"kea-dhcp6-server:config (container)\n"
" |\n"
" -- shared-networks (container)\n"
" |\n"
" -- shared-network (list instance)\n"
" |\n"
" -- name = foo\n"
" |\n"
" -- subnet6 (container)\n"
" |\n"
" -- subnet6 (list instance)\n"
" |\n"
" -- id = 123\n"
" |\n"
" -- subnet = 2001:db8::/48\n";
EXPECT_EQ(expected, tree->to_string(100));
// Check it validates.
EXPECT_NO_THROW(sess_->validate());
}
}; // end of anonymous namespace
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#include <yang/translator_shared_network.h>
#include <yang/adaptor.h>
#include <yang/yang_models.h>
#include <sstream>
using namespace std;
using namespace isc::data;
namespace isc {
namespace yang {
TranslatorSharedNetwork::TranslatorSharedNetwork(S_Session session,
const string& model)
: TranslatorBasic(session),
TranslatorOptionData(session, model),
TranslatorOptionDataList(session, model),
TranslatorPool(session, model),
TranslatorPools(session, model),
TranslatorPdPool(session, model),
TranslatorPdPools(session, model),
TranslatorHost(session, model),
TranslatorHosts(session, model),
TranslatorSubnet(session, model),
TranslatorSubnets(session, model),
model_(model) {
}
TranslatorSharedNetwork::~TranslatorSharedNetwork() {
}
ElementPtr
TranslatorSharedNetwork::getSharedNetwork(const string& xpath) {
try {
if (model_ == KEA_DHCP4_SERVER) {
return (getSharedNetworkKea(xpath, "subnet4"));
} else if (model_ == KEA_DHCP6_SERVER) {
return (getSharedNetworkKea(xpath, "subnet6"));
}
} catch (const sysrepo_exception& ex) {
isc_throw(SysrepoError,
"sysrepo error getting shared network at '" << xpath
<< "': " << ex.what());
}
isc_throw(NotImplemented,
"getSharedNetwork not implemented for the model: " << model_);
}
ElementPtr
TranslatorSharedNetwork::getSharedNetworkKea(const string& xpath,
const std::string& subsel) {
ElementPtr result = Element::createMap();
result->set("name", getItem(xpath + "/name"));
ConstElementPtr subnets = getSubnets(xpath + "/" + subsel);
if (subnets && (subnets->size() > 0)) {
result->set(subsel, subnets);
}
if (subsel == "subnet6") {
ConstElementPtr preferred = getItem(xpath + "/preferred-lifetime");
if (preferred) {
result->set("preferred-lifetime", preferred);
}
}
ConstElementPtr valid = getItem(xpath + "/valid-lifetime");
if (valid) {
result->set("valid-lifetime", valid);
}
ConstElementPtr renew = getItem(xpath + "/renew-timer");
if (renew) {
result->set("renew-timer", renew);
}
ConstElementPtr rebind = getItem(xpath + "/rebind-timer");
if (rebind) {
result->set("rebind-timer", rebind);
}
ConstElementPtr options = getOptionDataList(xpath + "/option-data-list");
if (options && (options->size() > 0)) {
result->set("option-data", options);
}
ConstElementPtr interface = getItem(xpath + "/interface");
if (interface) {
result->set("interface", interface);
}
if (subsel == "subnet6") {
ConstElementPtr interface_id = getItem(xpath + "/interface-id");
if (interface_id) {
result->set("interface-id", interface_id);
}
ConstElementPtr rapid_commit = getItem(xpath + "/rapid-commit");
if (rapid_commit) {
result->set("rapid-commit", rapid_commit);
}
}
ConstElementPtr guard = getItem(xpath + "/client-class");
if (guard) {
result->set("client-class", guard);
}
ConstElementPtr required = getItems(xpath + "/require-client-classes");
if (required && (required->size() > 0)) {
result->set("require-client-classes", required);
}
ConstElementPtr mode = getItem(xpath + "/reservation-mode");
if (mode) {
result->set("reservation-mode", mode);
}
ConstElementPtr relay = getItems(xpath + "/relay/ip-addresses");
if (relay && (relay->size() > 0)) {
ElementPtr relay_map = Element::createMap();
relay_map->set("ip-addresses", relay);
result->set("relay", relay_map);
}
if (subsel == "subnet4") {
ConstElementPtr match = getItem(xpath + "/match-client-id");
if (match) {
result->set("match-client-id", match);
}
ConstElementPtr next = getItem(xpath + "/next-server");
if (next) {
result->set("next-server", next);
}
ConstElementPtr hostname = getItem(xpath + "/server-hostname");
if (hostname) {
result->set("server-hostname", hostname);
}
ConstElementPtr boot = getItem(xpath + "/boot-file-name");
if (boot) {
result->set("boot-file-name", boot);
}
}
ConstElementPtr context = getItem(xpath + "/user-context");
if (context) {
result->set("user-context", Element::fromJSON(context->stringValue()));
}
return (result);
}
void
TranslatorSharedNetwork::setSharedNetwork(const string& xpath,
ConstElementPtr elem) {
try {
if (model_ == KEA_DHCP4_SERVER) {
setSharedNetworkKea(xpath, elem, "subnet4");
} else if (model_ == KEA_DHCP6_SERVER) {
setSharedNetworkKea(xpath, elem, "subnet6");
} else {
isc_throw(NotImplemented,
"setSharedNetwork not implemented for the model: "
<< model_);
}
} catch (const sysrepo_exception& ex) {
isc_throw(SysrepoError,
"sysrepo error setting shared network '" << elem->str()
<< "' at '" << xpath << "': " << ex.what());
}
}
void
TranslatorSharedNetwork::setSharedNetworkKea(const string& xpath,
ConstElementPtr elem,
const std::string& subsel) {
// Skip name which is the key.
ConstElementPtr subnets = elem->get(subsel);
if (subnets && (subnets->size() > 0)) {
setSubnets(xpath + "/" + subsel, subnets);
}
if (subsel == "subnet6") {
ConstElementPtr preferred = elem->get("preferred-lifetime");
if (preferred) {
setItem(xpath + "/preferred-lifetime", preferred, SR_UINT32_T);
}
}
ConstElementPtr valid = elem->get("valid-lifetime");
if (valid) {
setItem(xpath + "/valid-lifetime", valid, SR_UINT32_T);
}
ConstElementPtr renew = elem->get("renew-timer");
if (renew) {
setItem(xpath + "/renew-timer", renew, SR_UINT32_T);
}
ConstElementPtr rebind = elem->get("rebind-timer");
if (rebind) {
setItem(xpath + "/rebind-timer", rebind, SR_UINT32_T);
}
ConstElementPtr options = elem->get("option-data");
if (options && (options->size() > 0)) {
setOptionDataList(xpath + "/option-data-list", options);
}
ConstElementPtr interface = elem->get("interface");
if (interface) {
setItem(xpath + "/interface", interface, SR_STRING_T);
}
if (subsel == "subnet6") {
ConstElementPtr interface_id = elem->get("interface-id");
if (interface_id) {
setItem(xpath + "/interface-id", interface_id, SR_STRING_T);
}
ConstElementPtr rapid_commit = elem->get("rapid-commit");
if (rapid_commit) {
setItem(xpath + "/rapid-commit", rapid_commit, SR_BOOL_T);
}
}
ConstElementPtr guard = elem->get("client-class");
if (guard) {
setItem(xpath + "/client-class", guard, SR_STRING_T);
}
ConstElementPtr required = elem->get("require-client-classes");
if (required && (required->size() > 0)) {
for (ConstElementPtr rclass : required->listValue()) {
setItem(xpath + "/require-client-classes", rclass, SR_STRING_T);
}
}
ConstElementPtr mode = elem->get("reservation-mode");
if (mode) {
setItem(xpath + "/reservation-mode", mode, SR_ENUM_T);
}
ConstElementPtr relay = elem->get("relay");
if (relay) {
ConstElementPtr address = relay->get("ip-address");
ConstElementPtr addresses = relay->get("ip-addresses");
if (address) {
setItem(xpath + "/relay/ip-addresses", address, SR_STRING_T);
} else if (addresses && (addresses->size() > 0)) {
for (ConstElementPtr addr : addresses->listValue()) {
setItem(xpath + "/relay/ip-addresses", addr, SR_STRING_T);
}
}
}
if (subsel == "subnet4") {
ConstElementPtr match = elem->get("match-client-id");
if (match) {
setItem(xpath + "/match-client-id", match, SR_BOOL_T);
}
ConstElementPtr next = elem->get("next-server");
if (next) {
setItem(xpath + "/next-server", next, SR_STRING_T);
}
ConstElementPtr hostname = elem->get("server-hostname");
if (hostname) {
setItem(xpath + "/server-hostname", hostname, SR_STRING_T);
}
ConstElementPtr boot = elem->get("boot-file-name");
if (boot) {
setItem(xpath + "/boot-file-name", boot, SR_STRING_T);
}
}
ConstElementPtr context = Adaptor::getContext(elem);
if (context) {
ConstElementPtr repr = Element::create(context->str());
setItem(xpath + "/user-context", repr, SR_STRING_T);
}
}
TranslatorSharedNetworks::TranslatorSharedNetworks(S_Session session,
const string& model)
: TranslatorBasic(session),
TranslatorOptionData(session, model),
TranslatorOptionDataList(session, model),
TranslatorPool(session, model),
TranslatorPools(session, model),
TranslatorPdPool(session, model),
TranslatorPdPools(session, model),
TranslatorHost(session, model),
TranslatorHosts(session, model),
TranslatorSubnet(session, model),
TranslatorSubnets(session, model),
TranslatorSharedNetwork(session, model),
model_(model) {
}
TranslatorSharedNetworks::~TranslatorSharedNetworks() {
}
ElementPtr
TranslatorSharedNetworks::getSharedNetworks(const string& xpath) {
try {
ElementPtr result = Element::createList();
S_Iter_Value iter = getIter(xpath + "/*");
if (!iter) {
// Can't happen.
isc_throw(Unexpected, "getSharedNetworks: can't get iterator: "
<< xpath);
}
for (;;) {
const string& network = getNext(iter);
if (network.empty()) {
break;
}
result->add(getSharedNetwork(network));
}
return (result);
} catch (const sysrepo_exception& ex) {
isc_throw(SysrepoError,
"sysrepo error getting shared networks at '" << xpath
<< "': " << ex.what());
}
}
void
TranslatorSharedNetworks::setSharedNetworks(const string& xpath,
ConstElementPtr elem) {
try {
if ((model_ == KEA_DHCP4_SERVER) ||
(model_ == KEA_DHCP6_SERVER)) {
setSharedNetworksKea(xpath, elem);
} else {
isc_throw(NotImplemented,
"setSharedNetworks not implemented for the model: "
<< model_);
}
} catch (const sysrepo_exception& ex) {
isc_throw(SysrepoError,
"sysrepo error setting shared networks '" << elem->str()
<< "' at '" << xpath << "': " << ex.what());
}
}
void
TranslatorSharedNetworks::setSharedNetworksKea(const string& xpath,
ConstElementPtr elem) {
for (size_t i = 0; i < elem->size(); ++i) {
ConstElementPtr network = elem->get(i);
if (!network->contains("name")) {
isc_throw(BadValue, "setSharedNetworksKea requires name: "
<< network->str());
}
string name = network->get("name")->stringValue();
ostringstream key;
key<< xpath << "/shared-network[name='" << name << "']";
setSharedNetwork(key.str(), network);
}
}
}; // end of namespace isc::yang
}; // end of namespace isc
// Copyright (C) 2018 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
#ifndef ISC_TRANSLATOR_SHARED_NETWORK_H
#define ISC_TRANSLATOR_SHARED_NETWORK_H 1
#include <yang/translator.h>
#include <yang/translator_subnet.h>
#include <list>
namespace isc {
namespace yang {
/// Shared network translation between YANG and JSON
///
/// JSON syntax for kea-dhcp4 is:
/// @code
/// {
/// "name": <name>,
/// "subnet4": <subnet list>,
/// "valid-lifetime": <valid lifetime>,
/// "renew-timer": <renew timer>,
/// "rebind-timer": <rebind timer>,
/// "option-data": [ <list of option data> ],
/// "interface": "<interface>",
/// "client-class": "<guard class name>",
/// "require-client-classes": [ <list of required class names> ],
/// "reservation-mode": <host reservation mode>,
/// "relay": <relay ip address(es)>,
/// "match-client-id": <match client id flag>,
/// "next-server": "<next server>",
/// "server-hostname": "<server hostname>",
/// "boot-file-name": "<boot file name>",
/// "user-context": { <json map> },
/// "comment": "<comment>"
/// }
/// @endcode
///
/// JSON syntax for kea-dhcp6 is:
/// @code
/// {
/// "name": <name>,
/// "subnet6": <subnet list>,
/// "preferred-lifetime": <preferred lifetime>,
/// "valid-lifetime": <valid lifetime>,
/// "renew-timer": <renew timer>,
/// "rebind-timer": <rebind timer>,
/// "option-data": [ <list of option data> ],
/// "interface": "<interface>",
/// "interface-id": "<interface id>",
/// "rapid-commit": <rapid commit flag>,
/// "client-class": "<guard class name>",
/// "require-client-classes": [ <list of required class names> ],
/// "reservation-mode": <host reservation mode>,
/// "relay": <relay ip address(es)>,
/// "user-context": { <json map> },
/// "comment": "<comment>"
/// }
/// @endcode
///
/// YANG syntax for kea-dhcp[46] is with name as the list key:
/// @code
/// +--rw name string
/// +--rw valid-lifetime? uint32
/// +--rw renew-timer? uint32
/// +--rw rebind-timer? uint32
/// +--rw option-data-list option-data*
/// +--rw interface? string
/// +--rw client-class? string
/// +--rw require-client-classes* string
/// +--rw reservation-mode? enumeration
/// +--rw relay ip-addresses*
/// +--rw user-context? string
/// (DHCPv4 only)
/// +--rw subnet4 subnet4*
/// +--rw match-client-id? boolean
/// +--rw next-server? inet:ipv4-address
/// +--rw server-hostname? string
/// +--rw boot-file-name? string
/// (DHCPv6 only)
/// +--rw subnet6 subnet6*
/// +--rw preferred-lifetime? uint32
/// +--rw interface-id? string
/// +--rw rapid-commit? boolean
/// @endcode
///
/// An example in JSON and YANG formats:
/// @code
/// [
/// {
/// "name": "foo",
/// "subnet6":
/// [
/// {
/// "subnet": "2001:db8::/48",
/// "id": 123
/// }
/// ]
/// }
/// ]
/// @endcode
/// @code
/// /kea-dhcp6-server:config (container)
/// /kea-dhcp6-server:config/shared-networks (container)
/// /kea-dhcp6-server:config/shared-networks/
/// shared-network[name='foo'] (list instance)
/// /kea-dhcp6-server:config/shared-networks/shared-network[name='foo']/
/// name = foo
/// /kea-dhcp6-server:config/shared-networks/shared-network[name='foo']/
/// subnet6 (container)
/// /kea-dhcp6-server:config/shared-networks/shared-network[name='foo']/
/// subnet6/subnet6[id='123'] (list instance)
/// /kea-dhcp6-server:config/shared-networks/shared-network[name='foo']/
/// subnet6/subnet6[id='123']/id = 123
/// /kea-dhcp6-server:config/shared-networks/shared-network[name='foo']/
/// subnet6/subnet6[id='123']/subnet = 2001:db8::/48
/// @endcode
/// @brief A translator class for converting a shared network between
/// YANG and JSON.
///
/// Currently supports on kea-dhcp[46]-server. Does not exist in
/// ietf-dhcpv6-server.
class TranslatorSharedNetwork : virtual public TranslatorSubnets {
public:
/// @brief Constructor.
///
/// @param session Sysrepo session.
/// @param model Model name.
TranslatorSharedNetwork(S_Session session, const std::string& model);
/// @brief Destructor.
virtual ~TranslatorSharedNetwork();
/// @brief Get and translate a shared network from YANG to JSON.
///
/// @param xpath The xpath of the shared network.
/// @return JSON representation of the shared network.
/// @throw SysrepoError when sysrepo raises an error.
isc::data::ElementPtr getSharedNetwork(const std::string& xpath);
/// @brief Translate and set shared network from JSON to YANG.
///
/// @param xpath The xpath of the shared network.
/// @param elem The JSON element.
void setSharedNetwork(const std::string& xpath,
isc::data::ConstElementPtr elem);
protected:
/// @brief getSharedNetwork for kea-dhcp[46].
///
/// @param xpath The xpath of the shared network.
/// @param subsel The subnet list name.
/// @return JSON representation of the shared network.
isc::data::ElementPtr getSharedNetworkKea(const std::string& xpath,
const std::string& subsel);
/// @brief setSharedNetwork for kea-dhcp[46].
///
/// @param xpath The xpath of the shared network.
/// @param elem The JSON element.
/// @param subsel The subnet list name.
void setSharedNetworkKea(const std::string& xpath,
isc::data::ConstElementPtr elem,
const std::string& subsel);
/// @brief The model.
std::string model_;
};
/// @brief A translator class for converting a shared network list between
/// YANG and JSON.
///
/// Currently supports on kea-dhcp[46]-server. Does not exist in
/// ietf-dhcpv6-server.
class TranslatorSharedNetworks : virtual public TranslatorSharedNetwork {
public:
/// @brief Constructor.
///
/// @param session Sysrepo session.
/// @param model Model name.