cloudnet: initial version

This commit is contained in:
Yousong Zhou
2019-09-12 08:49:03 +00:00
parent 5b8fdcd5af
commit 0b6d33d4fc
43 changed files with 3778 additions and 0 deletions
@@ -0,0 +1,10 @@
region = 'Yunion'
address = '10.168.222.136'
port = 8891
auth_uri = 'http://10.168.222.136:35357/v3'
admin_user = 'cloudnetadmin'
admin_password = 'xxxxxxxxxxxxxxxx'
admin_tenant_name = 'system'
sql_connection = 'mysql+pymysql://yunionansible:9YuQL9KEZn1mppaK@localhost:3306/yunionansible?charset=utf8'
auto_sync_table = True
+1
View File
@@ -0,0 +1 @@
DESCRIPTION="Yunion Cloudnet"
+1
View File
@@ -32,6 +32,7 @@ import (
"yunion.io/x/onecloud/cmd/climc/promputils"
"yunion.io/x/onecloud/cmd/climc/shell"
_ "yunion.io/x/onecloud/cmd/climc/shell/cloudnet"
_ "yunion.io/x/onecloud/cmd/climc/shell/etcd"
_ "yunion.io/x/onecloud/cmd/climc/shell/k8s"
"yunion.io/x/onecloud/pkg/mcclient"
+13
View File
@@ -0,0 +1,13 @@
package cloudnet
import (
"yunion.io/x/onecloud/cmd/climc/shell"
"yunion.io/x/onecloud/pkg/util/printutils"
)
var (
R = shell.R
printList = printutils.PrintJSONList
printObject = printutils.PrintJSONObject
printBatchResults = printutils.PrintJSONBatchResults
)
+82
View File
@@ -0,0 +1,82 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet"
base_options "yunion.io/x/onecloud/pkg/mcclient/options"
options "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet"
)
func init() {
R(&options.MeshNetworkCreateOptions{}, "meshnetwork-create", "Create mesh network", func(s *mcclient.ClientSession, opts *options.MeshNetworkCreateOptions) error {
params, err := base_options.StructToParams(opts)
if err != nil {
return err
}
mn, err := modules.MeshNetworks.Create(s, params)
if err != nil {
return err
}
printObject(mn)
return nil
})
R(&options.MeshNetworkGetOptions{}, "meshnetwork-show", "Show mesh network", func(s *mcclient.ClientSession, opts *options.MeshNetworkGetOptions) error {
mn, err := modules.MeshNetworks.Get(s, opts.ID, nil)
if err != nil {
return err
}
printObject(mn)
return nil
})
R(&options.MeshNetworkListOptions{}, "meshnetwork-list", "List mesh networks", func(s *mcclient.ClientSession, opts *options.MeshNetworkListOptions) error {
params, err := base_options.ListStructToParams(opts)
if err != nil {
return err
}
result, err := modules.MeshNetworks.List(s, params)
if err != nil {
return err
}
printList(result, modules.MeshNetworks.GetColumns(s))
return nil
})
R(&options.MeshNetworkUpdateOptions{}, "meshnetwork-update", "Update mesh network", func(s *mcclient.ClientSession, opts *options.MeshNetworkUpdateOptions) error {
params, err := base_options.StructToParams(opts)
mn, err := modules.MeshNetworks.Update(s, opts.ID, params)
if err != nil {
return err
}
printObject(mn)
return nil
})
R(&options.MeshNetworkDeleteOptions{}, "meshnetwork-delete", "Delete mesh network", func(s *mcclient.ClientSession, opts *options.MeshNetworkDeleteOptions) error {
mn, err := modules.MeshNetworks.Delete(s, opts.ID, nil)
if err != nil {
return err
}
printObject(mn)
return nil
})
R(&options.MeshNetworkActionRealizeOptions{}, "meshnetwork-realize", "Realize mesh network", func(s *mcclient.ClientSession, opts *options.MeshNetworkActionRealizeOptions) error {
mn, err := modules.MeshNetworks.PerformAction(s, opts.ID, "realize", nil)
if err != nil {
return err
}
printObject(mn)
return nil
})
}
+134
View File
@@ -0,0 +1,134 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet"
base_options "yunion.io/x/onecloud/pkg/mcclient/options"
options "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet"
)
func init() {
R(&options.RouterCreateOptions{}, "router-create", "Create router", func(s *mcclient.ClientSession, opts *options.RouterCreateOptions) error {
params, err := opts.Params()
if err != nil {
return err
}
router, err := modules.Routers.Create(s, params)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouterGetOptions{}, "router-show", "Show router", func(s *mcclient.ClientSession, opts *options.RouterGetOptions) error {
router, err := modules.Routers.Get(s, opts.ID, nil)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouterListOptions{}, "router-list", "List routers", func(s *mcclient.ClientSession, opts *options.RouterListOptions) error {
params, err := base_options.ListStructToParams(opts)
if err != nil {
return err
}
result, err := modules.Routers.List(s, params)
if err != nil {
return err
}
printList(result, modules.Routers.GetColumns(s))
return nil
})
R(&options.RouterUpdateOptions{}, "router-update", "Update router", func(s *mcclient.ClientSession, opts *options.RouterUpdateOptions) error {
params, err := base_options.StructToParams(opts)
router, err := modules.Routers.Update(s, opts.ID, params)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouterDeleteOptions{}, "router-delete", "Delete router", func(s *mcclient.ClientSession, opts *options.RouterDeleteOptions) error {
router, err := modules.Routers.Delete(s, opts.ID, nil)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouterActionJoinMeshNetworkOptions{}, "router-join-meshnetwork", "Router join meshnetwork", func(s *mcclient.ClientSession, opts *options.RouterActionJoinMeshNetworkOptions) error {
params, err := base_options.StructToParams(opts)
if err != nil {
return err
}
router, err := modules.Routers.PerformAction(s, opts.ID, "join-mesh-network", params)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouterActionLeaveMeshNetworkOptions{}, "router-leave-meshnetwork", "Router leave meshnetwork", func(s *mcclient.ClientSession, opts *options.RouterActionLeaveMeshNetworkOptions) error {
params, err := base_options.StructToParams(opts)
if err != nil {
return err
}
router, err := modules.Routers.PerformAction(s, opts.ID, "leave-mesh-network", params)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouterActionRegisterIfnameOptions{}, "router-register-ifname", "Router register new ifname", func(s *mcclient.ClientSession, opts *options.RouterActionRegisterIfnameOptions) error {
params, err := base_options.StructToParams(opts)
if err != nil {
return err
}
router, err := modules.Routers.PerformAction(s, opts.ID, "register-ifname", params)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouterActionUnregisterIfnameOptions{}, "router-unregister-ifname", "Router unregister ifname", func(s *mcclient.ClientSession, opts *options.RouterActionUnregisterIfnameOptions) error {
params, err := base_options.StructToParams(opts)
if err != nil {
return err
}
router, err := modules.Routers.PerformAction(s, opts.ID, "unregister-ifname", params)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouterActionRealizeOptions{}, "router-realize", "Router realize", func(s *mcclient.ClientSession, opts *options.RouterActionRealizeOptions) error {
params, err := base_options.StructToParams(opts)
if err != nil {
return err
}
router, err := modules.Routers.PerformAction(s, opts.ID, "realize", params)
if err != nil {
return err
}
printObject(router)
return nil
})
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet"
base_options "yunion.io/x/onecloud/pkg/mcclient/options"
options "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet"
)
func init() {
R(&options.RouteCreateOptions{}, "router-route-create", "Create router", func(s *mcclient.ClientSession, opts *options.RouteCreateOptions) error {
params, err := base_options.StructToParams(opts)
if err != nil {
return err
}
router, err := modules.Routes.Create(s, params)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouteGetOptions{}, "router-route-show", "Show router", func(s *mcclient.ClientSession, opts *options.RouteGetOptions) error {
router, err := modules.Routes.Get(s, opts.ID, nil)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouteListOptions{}, "router-route-list", "List routers", func(s *mcclient.ClientSession, opts *options.RouteListOptions) error {
params, err := base_options.ListStructToParams(opts)
if err != nil {
return err
}
result, err := modules.Routes.List(s, params)
if err != nil {
return err
}
printList(result, modules.Routes.GetColumns(s))
return nil
})
R(&options.RouteUpdateOptions{}, "router-route-update", "Update router", func(s *mcclient.ClientSession, opts *options.RouteUpdateOptions) error {
params, err := base_options.StructToParams(opts)
router, err := modules.Routes.Update(s, opts.ID, params)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RouteDeleteOptions{}, "router-route-delete", "Delete router", func(s *mcclient.ClientSession, opts *options.RouteDeleteOptions) error {
router, err := modules.Routes.Delete(s, opts.ID, nil)
if err != nil {
return err
}
printObject(router)
return nil
})
}
+74
View File
@@ -0,0 +1,74 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient"
modules "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet"
base_options "yunion.io/x/onecloud/pkg/mcclient/options"
options "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet"
)
func init() {
R(&options.RuleCreateOptions{}, "router-rule-create", "Create router rule", func(s *mcclient.ClientSession, opts *options.RuleCreateOptions) error {
params, err := base_options.StructToParams(opts)
if err != nil {
return err
}
router, err := modules.Rules.Create(s, params)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RuleGetOptions{}, "router-rule-show", "Show router rule", func(s *mcclient.ClientSession, opts *options.RuleGetOptions) error {
router, err := modules.Rules.Get(s, opts.ID, nil)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RuleListOptions{}, "router-rule-list", "List router rules", func(s *mcclient.ClientSession, opts *options.RuleListOptions) error {
params, err := base_options.ListStructToParams(opts)
if err != nil {
return err
}
result, err := modules.Rules.List(s, params)
if err != nil {
return err
}
printList(result, modules.Rules.GetColumns(s))
return nil
})
R(&options.RuleUpdateOptions{}, "router-rule-update", "Update router rule", func(s *mcclient.ClientSession, opts *options.RuleUpdateOptions) error {
params, err := base_options.StructToParams(opts)
router, err := modules.Rules.Update(s, opts.ID, params)
if err != nil {
return err
}
printObject(router)
return nil
})
R(&options.RuleDeleteOptions{}, "router-rule-delete", "Delete router rule", func(s *mcclient.ClientSession, opts *options.RuleDeleteOptions) error {
router, err := modules.Rules.Delete(s, opts.ID, nil)
if err != nil {
return err
}
printObject(router)
return nil
})
}
+23
View File
@@ -0,0 +1,23 @@
// Copyright 2019 Yunion
//
// 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.
package main
import (
"yunion.io/x/onecloud/pkg/cloudnet/service"
)
func main() {
service.StartService()
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2019 Yunion
//
// 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.
package models // import "yunion.io/x/onecloud/pkg/cloudnet/models"
+212
View File
@@ -0,0 +1,212 @@
// Copyright 2019 Yunion
//
// 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.
package models
import (
"context"
"fmt"
"net"
"strings"
"yunion.io/x/log"
yerrors "yunion.io/x/pkg/util/errors"
"yunion.io/x/pkg/util/netutils"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
)
type SIfacePeer struct {
db.SStandaloneResourceBase
RouterId string
IfaceId string
PeerIfaceId string
PeerRouterId string
PublicKey string
AllowedIPs string
Endpoint string
PersistentKeepalive int
}
type SIfacePeerManager struct {
db.SStandaloneResourceBaseManager
}
var IfacePeerManager *SIfacePeerManager
func init() {
IfacePeerManager = &SIfacePeerManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SIfacePeer{},
"ifacepeers_tbl",
"ifacepeer",
"ifacepeers",
),
}
IfacePeerManager.SetVirtualObject(IfacePeerManager)
}
func (ifacePeer *SIfacePeer) subnetsStrList() []string {
return strings.Split(ifacePeer.AllowedIPs, ",")
}
func (ifacePeer *SIfacePeer) subnetsParsed() Subnets {
parts := ifacePeer.subnetsStrList()
r := make([]*netutils.IPV4Prefix, 0, len(parts))
for _, part := range parts {
p, err := netutils.NewIPV4Prefix(part)
if err != nil {
log.Errorf("%s: invalid subnet sneaked in: %s", ifacePeer.Id, part)
return nil
}
r = append(r, &p)
}
return Subnets(r)
}
func (man *SIfacePeerManager) removeByPeerIface(ctx context.Context, userCred mcclient.TokenCredential, iface *SIface) error {
peers := []SIfacePeer{}
q := man.Query().Equals("peer_iface_id", iface.Id)
if err := db.FetchModelObjects(IfacePeerManager, q, &peers); err != nil {
return err
}
var errs []error
for j := range peers {
if err := peers[j].Delete(ctx, userCred); err != nil {
errs = append(errs, err)
}
}
return yerrors.NewAggregate(errs)
}
func (man *SIfacePeerManager) removeByIface(ctx context.Context, userCred mcclient.TokenCredential, iface *SIface) error {
peers := []SIfacePeer{}
q := man.Query().Equals("iface_id", iface.Id)
if err := db.FetchModelObjects(IfacePeerManager, q, &peers); err != nil {
return err
}
var errs []error
for j := range peers {
if err := peers[j].Delete(ctx, userCred); err != nil {
errs = append(errs, err)
}
}
return yerrors.NewAggregate(errs)
}
func (man *SIfacePeerManager) getByFilter(filter map[string]string) ([]SIfacePeer, error) {
ifacePeers := []SIfacePeer{}
q := man.Query()
for key, val := range filter {
q = q.Equals(key, val)
}
if err := db.FetchModelObjects(IfacePeerManager, q, &ifacePeers); err != nil {
return nil, err
}
return ifacePeers, nil
}
func (man *SIfacePeerManager) getOneByFilter(filter map[string]string) (*SIfacePeer, error) {
ifacePeers, err := man.getByFilter(filter)
if err != nil {
return nil, err
}
if len(ifacePeers) == 0 {
return nil, errNotFound(fmt.Errorf("cannot find iface peer: %#v", filter))
}
if len(ifacePeers) > 1 {
return nil, errMoreThanOne(fmt.Errorf("found more than 1 iface peers: %#v", filter))
}
return &ifacePeers[0], nil
}
func (man *SIfacePeerManager) getByIface(iface *SIface) ([]SIfacePeer, error) {
filter := map[string]string{
"router_id": iface.RouterId,
"iface_id": iface.Id,
}
ifacePeers, err := man.getByFilter(filter)
if err != nil {
return nil, err
}
return ifacePeers, nil
}
func (man *SIfacePeerManager) getByIfacePublicKey(iface *SIface, pubkey string) (*SIfacePeer, error) {
filter := map[string]string{
"router_id": iface.RouterId,
"iface_id": iface.Id,
"public_key": pubkey,
}
ifacePeer, err := man.getOneByFilter(filter)
if err != nil {
return nil, err
}
return ifacePeer, nil
}
func (man *SIfacePeerManager) checkAllowedIPs(iface *SIface, oldPeer *SIfacePeer, allowedNets Subnets) error {
ifacePeers, err := man.getByIface(iface)
if err != nil {
return err
}
for i := range ifacePeers {
ifacePeer := &ifacePeers[i]
if oldPeer != nil && oldPeer.Id == ifacePeer.Id {
continue
}
existingNets := ifacePeer.subnetsParsed()
if _, net := existingNets.ContainsAnyEx(allowedNets); net != nil {
return fmt.Errorf("subnet %s is already occupied by peer %s(%s)",
net, ifacePeer.Name, ifacePeer.Id)
}
}
return nil
}
func (man *SIfacePeerManager) updateEndpointIPByPeerRouter(ctx context.Context, endpointIP string, router *SRouter) error {
filter := map[string]string{
"peer_router_id": router.Id,
}
ifacePeers, err := man.getByFilter(filter)
if err != nil {
return err
}
var errs []error
for i := range ifacePeers {
ifacePeer := &ifacePeers[i]
host, port, err := net.SplitHostPort(ifacePeer.Endpoint)
if err != nil {
errs = append(errs, err)
continue
}
if host == endpointIP {
continue
}
_, err = db.Update(ifacePeer, func() error {
endpoint := net.JoinHostPort(endpointIP, port)
ifacePeer.Endpoint = endpoint
return nil
})
if err != nil {
errs = append(errs, err)
}
}
return yerrors.NewAggregate(errs)
}
+420
View File
@@ -0,0 +1,420 @@
// Copyright 2019 Yunion
//
// 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.
package models
import (
"context"
"fmt"
"regexp"
"strconv"
"strings"
"github.com/pkg/errors"
"yunion.io/x/jsonutils"
yerrors "yunion.io/x/pkg/util/errors"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
cnutils "yunion.io/x/onecloud/pkg/cloudnet/utils"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/pkg/util/netutils"
)
var regexpIfname = regexp.MustCompile(`[A-Za-z][A-Za-z0-9]{0,14}`)
type SIface struct {
db.SStandaloneResourceBase
RouterId string `length:"32" nullable:"false"`
NetworkId string `length:"32" nullable:"false"`
Ifname string `length:"32" nullable:"false"`
PrivateKey string
PublicKey string
ListenPort int `nullable:"false"`
IsSystem bool `nullable:"false"`
}
type SIfaceManager struct {
db.SStandaloneResourceBaseManager
}
var IfaceManager *SIfaceManager
func init() {
IfaceManager = &SIfaceManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SIface{},
"ifaces_tbl",
"iface",
"ifaces",
),
}
IfaceManager.SetVirtualObject(IfaceManager)
}
func (man *SIfaceManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
// router existence
// PrivateKey validation , or generation
// ListenPort validation, uniqueness
// ListenPort generation
return nil, errors.New("manually adding interface is currently not supported")
}
func (man *SIfaceManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
q, err := man.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query)
if err != nil {
return nil, err
}
data := query.(*jsonutils.JSONDict)
q, err = validators.ApplyModelFilters(q, data, []*validators.ModelFilterOptions{
{Key: "router", ModelKeyword: "router", OwnerId: userCred},
})
if err != nil {
return nil, err
}
return q, nil
}
func (iface *SIface) ValidateUpdateCondition(ctx context.Context) error {
// same as create but no generation
// if privatekey updated
// update peers whose peerifaceid == self.id
return nil
}
func (iface *SIface) ValidateDeleteCondition(ctx context.Context) error {
// if networkid != "" {
// return errors.New("part of network, remove it by remove network memeber")
// }
return nil
}
func (iface *SIface) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
// remove ifacepeer whose peerifaceId is self.id
return nil
}
func (iface *SIface) addOrUpdatePeer(ctx context.Context, userCred mcclient.TokenCredential,
peerIface *SIface, allowedNets Subnets, peerRouter *SRouter) error {
// XXX lock
endpoint := ""
endpointIP := peerRouter.endpointIP()
if endpointIP != "" && peerIface.ListenPort > 0 {
endpoint = fmt.Sprintf("%s:%d", endpointIP, peerIface.ListenPort)
}
persistentKeepalive := 0
if endpointIP != "" {
// persistent keepalive from private addr to exit addr
router, err := iface.getRouter()
if err != nil {
return errors.WithMessagef(err, "get iface router %s", iface.RouterId)
}
myIP := router.endpointIP()
if myIP != "" {
myIPAddr, err := netutils.NewIPV4Addr(myIP)
if err != nil {
return err
}
if netutils.IsPrivate(myIPAddr) {
peerIPAddr, err := netutils.NewIPV4Addr(endpointIP)
if err != nil {
return err
}
if netutils.IsExitAddress(peerIPAddr) {
persistentKeepalive = 10
}
}
}
}
ifacePeer, err := IfacePeerManager.getByIfacePublicKey(iface, peerIface.PublicKey)
if err != nil && !IsNotFound(err) {
return err
}
if err := IfacePeerManager.checkAllowedIPs(iface, ifacePeer, allowedNets); err != nil {
return err
}
if ifacePeer == nil {
ifacePeer := &SIfacePeer{
RouterId: iface.RouterId,
IfaceId: iface.Id,
PeerIfaceId: peerIface.Id,
PeerRouterId: peerIface.RouterId,
PublicKey: peerIface.PublicKey,
AllowedIPs: allowedNets.String(),
Endpoint: endpoint,
PersistentKeepalive: persistentKeepalive,
}
ifacePeer.Name = fmt.Sprintf("%s-%s", iface.Name, peerIface.Name)
err := IfacePeerManager.TableSpec().Insert(ifacePeer)
return err
}
_, err = db.Update(ifacePeer, func() error {
ifacePeer.PeerIfaceId = peerIface.Id
ifacePeer.PeerRouterId = peerIface.RouterId
ifacePeer.Endpoint = endpoint
ifacePeer.AllowedIPs = allowedNets.String()
ifacePeer.PersistentKeepalive = persistentKeepalive
return nil
})
return err
}
func (iface *SIface) clearPeers(ctx context.Context, userCred mcclient.TokenCredential) error {
return IfacePeerManager.removeByIface(ctx, userCred, iface)
}
func (iface *SIface) clearPeerRefs(ctx context.Context, userCred mcclient.TokenCredential) error {
return IfacePeerManager.removeByPeerIface(ctx, userCred, iface)
}
func (iface *SIface) clearRoutes(ctx context.Context, userCred mcclient.TokenCredential) error {
return RouteManager.removeByIface(ctx, userCred, iface)
}
func (iface *SIface) remove(ctx context.Context, userCred mcclient.TokenCredential) error {
var errs []error
if err := iface.clearRoutes(ctx, userCred); err != nil {
errs = append(errs, err)
}
if err := iface.clearPeers(ctx, userCred); err != nil {
errs = append(errs, err)
}
if err := iface.clearPeerRefs(ctx, userCred); err != nil {
errs = append(errs, err)
}
if len(errs) == 0 {
if err := iface.Delete(ctx, userCred); err != nil {
errs = append(errs, err)
}
}
return yerrors.NewAggregate(errs)
}
func (iface *SIface) isTypeWireguard() bool {
r := iface.ListenPort > 0 && iface.PrivateKey != "" && iface.PublicKey != ""
return r
}
func (iface *SIface) getRouter() (*SRouter, error) {
obj, err := db.FetchById(RouterManager, iface.RouterId)
if err != nil {
return nil, err
}
router := obj.(*SRouter)
return router, nil
}
func (man *SIfaceManager) removeByFilter(ctx context.Context, userCred mcclient.TokenCredential, filter map[string]string) error {
ifaces, err := man.getByFilter(filter)
if err != nil {
return err
}
var errs []error
for i := range ifaces {
iface := &ifaces[i]
if err := iface.remove(ctx, userCred); err != nil {
errs = append(errs, err)
}
}
return yerrors.NewAggregate(errs)
}
func (man *SIfaceManager) removeByRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
err := man.removeByFilter(ctx, userCred, map[string]string{
"router_id": router.Id,
})
return err
}
func (man *SIfaceManager) removeByMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork) error {
err := man.removeByFilter(ctx, userCred, map[string]string{
"network_id": mn.Id,
})
return err
}
func (man *SIfaceManager) removeByMeshNetworkRouter(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork, router *SRouter) error {
err := man.removeByFilter(ctx, userCred, map[string]string{
"network_id": mn.Id,
"router_id": router.Id,
})
return err
}
func (man *SIfaceManager) getByRouter(router *SRouter) ([]SIface, error) {
return man.getByFilter(map[string]string{
"router_id": router.Id,
})
}
func (man *SIfaceManager) getByRouterIfname(router *SRouter, ifname string) (*SIface, error) {
return man.getOneByFilter(map[string]string{
"router_id": router.Id,
"ifname": ifname,
})
}
func (man *SIfaceManager) getByFilter(filter map[string]string) ([]SIface, error) {
ifaces := []SIface{}
q := man.Query()
for key, val := range filter {
q = q.Equals(key, val)
}
if err := db.FetchModelObjects(IfaceManager, q, &ifaces); err != nil {
return nil, err
}
return ifaces, nil
}
func (man *SIfaceManager) getOneByFilter(filter map[string]string) (*SIface, error) {
ifaces, err := man.getByFilter(filter)
if err != nil {
return nil, err
}
if len(ifaces) == 0 {
return nil, fmt.Errorf("cannot find iface for condition: %#v", filter)
}
if len(ifaces) > 1 {
return nil, fmt.Errorf("found more than 1 ifaces for condition: %#v", filter)
}
return &ifaces[0], nil
}
func (man *SIfaceManager) checkExistenceByFilter(filter map[string]string) error {
ifaces, err := man.getByFilter(filter)
if err != nil {
return err
}
if len(ifaces) > 0 {
return fmt.Errorf("iface exist: %s(%s)", ifaces[0].Name, ifaces[0].Id)
}
return nil
}
func (man *SIfaceManager) getByRouterNetwork(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, mn *SMeshNetwork) ([]SIface, error) {
ifaces, err := man.getByFilter(map[string]string{
"router_id": router.Id,
"network_id": mn.Id,
})
return ifaces, err
}
func (man *SIfaceManager) getOneByRouterNetwork(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, mn *SMeshNetwork) (*SIface, error) {
iface, err := man.getOneByFilter(map[string]string{
"router_id": router.Id,
"network_id": mn.Id,
})
return iface, err
}
func (man *SIfaceManager) getByMeshNetworkMember(member *SMeshNetworkMember) (*SIface, error) {
iface, err := man.getOneByFilter(map[string]string{
"router_id": member.RouterId,
"network_id": member.MeshNetworkId,
})
return iface, err
}
func (man *SIfaceManager) getNextName(filter map[string]string, base string) (string, error) {
ifaces, err := man.getByFilter(filter)
if err != nil {
return "", err
}
occupied := map[int]struct{}{}
for i := range ifaces {
iface := &ifaces[i]
if strings.HasPrefix(iface.Ifname, base) {
istr := iface.Ifname[len(base):]
i, err := strconv.ParseUint(istr, 10, 16)
if err != nil {
continue
}
occupied[int(i)] = struct{}{}
}
}
for i := 0; i < 65536; i++ {
if _, ok := occupied[i]; !ok {
return fmt.Sprintf("%s%d", base, i), nil
}
}
return "", fmt.Errorf("all names occupied")
}
func (man *SIfaceManager) addWireguardIface(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, mn *SMeshNetwork) (*SIface, error) {
k := cnutils.MustNewKey()
port := router.mustFindFreePort(ctx)
iface := &SIface{
RouterId: router.Id,
PrivateKey: k.String(),
PublicKey: k.PublicKey().String(),
ListenPort: port,
}
iface.IsSystem = true
{ // ifname
if name, err := man.getNextName(map[string]string{
"router_id": router.Id,
}, "wg"); err != nil {
return nil, err
} else {
iface.Ifname = name
}
}
{ // obj name
name := router.Name + "-"
if mn != nil {
iface.NetworkId = mn.Id
name += mn.Name + "-"
}
name += fmt.Sprintf("%d", port)
iface.Name = name
}
iface.SetModelManager(man, iface)
err := man.TableSpec().Insert(iface)
if err != nil {
return nil, err
}
if err := RuleManager.addWireguardIfaceRules(ctx, userCred, iface); err != nil {
iface.Delete(ctx, userCred)
return nil, err
}
return iface, nil
}
func (man *SIfaceManager) addIface(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, ifname string) (*SIface, error) {
if err := man.checkExistenceByFilter(map[string]string{
"router_id": router.Id,
"ifname": ifname,
}); err != nil {
return nil, err
}
iface := &SIface{
RouterId: router.Id,
Ifname: ifname,
}
iface.SetModelManager(man, iface)
err := man.TableSpec().Insert(iface)
if err != nil {
return nil, err
}
return iface, nil
}
+19
View File
@@ -0,0 +1,19 @@
// Copyright 2019 Yunion
//
// 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.
package models
func InitDB() error {
return nil
}
+152
View File
@@ -0,0 +1,152 @@
// Copyright 2019 Yunion
//
// 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.
package models
import (
"context"
"fmt"
"strings"
"yunion.io/x/log"
yerrors "yunion.io/x/pkg/util/errors"
"yunion.io/x/pkg/util/netutils"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
)
type SMeshNetworkMember struct {
db.SStandaloneResourceBase
MeshNetworkId string
RouterId string
AdvertiseSubnets string
}
type SMeshNetworkMemberManager struct {
db.SStandaloneResourceBaseManager
}
var MeshNetworkMemberManager *SMeshNetworkMemberManager
func init() {
MeshNetworkMemberManager = &SMeshNetworkMemberManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SMeshNetworkMember{},
"meshnetwork_members_tbl",
"meshnetwork_member",
"meshnetwork_members",
),
}
MeshNetworkMemberManager.SetVirtualObject(MeshNetworkMemberManager)
}
func (member *SMeshNetworkMember) subnetsStrList() []string {
return strings.Split(member.AdvertiseSubnets, ",")
}
func (member *SMeshNetworkMember) subnetsParsed() Subnets {
parts := member.subnetsStrList()
r := make([]*netutils.IPV4Prefix, 0, len(parts))
for _, part := range parts {
p, err := netutils.NewIPV4Prefix(part)
if err != nil {
log.Errorf("%s: invalid subnet sneaked in: %s", member.Id, part)
return nil
}
r = append(r, &p)
}
return Subnets(r)
}
func (member *SMeshNetworkMember) getRouter() (*SRouter, error) {
obj, err := db.FetchById(RouterManager, member.RouterId)
if err != nil {
return nil, err
}
router := obj.(*SRouter)
return router, nil
}
func (man *SMeshNetworkMemberManager) getByFilter(filter map[string]string) ([]SMeshNetworkMember, error) {
members := []SMeshNetworkMember{}
q := man.Query()
for key, val := range filter {
q = q.Equals(key, val)
}
if err := db.FetchModelObjects(man, q, &members); err != nil {
return nil, err
}
return members, nil
}
func (man *SMeshNetworkMemberManager) removeByFilter(ctx context.Context, userCred mcclient.TokenCredential, filter map[string]string) error {
members, err := man.getByFilter(filter)
if err != nil {
return err
}
var errs []error
for i := range members {
if err := members[i].Delete(ctx, userCred); err != nil {
errs = append(errs, err)
}
}
return yerrors.NewAggregate(errs)
}
func (man *SMeshNetworkMemberManager) removeByRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
err := man.removeByFilter(ctx, userCred, map[string]string{
"router_id": router.Id,
})
return err
}
func (man *SMeshNetworkMemberManager) removeByMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork) error {
err := man.removeByFilter(ctx, userCred, map[string]string{
"mesh_network_id": mn.Id,
})
return err
}
func (man *SMeshNetworkMemberManager) removeByMeshNetworkRouter(ctx context.Context, userCred mcclient.TokenCredential,
mn *SMeshNetwork, router *SRouter) error {
err := man.removeByFilter(ctx, userCred, map[string]string{
"mesh_network_id": mn.Id,
"router_id": router.Id,
})
return err
}
func (man *SMeshNetworkMemberManager) getMemebersByMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork) ([]SMeshNetworkMember, error) {
members := []SMeshNetworkMember{}
q := man.Query().Equals("mesh_network_id", mn.Id)
err := db.FetchModelObjects(man, q, &members)
if err != nil {
return nil, err
}
return members, nil
}
func (man *SMeshNetworkMemberManager) addMember(ctx context.Context, userCred mcclient.TokenCredential, mn *SMeshNetwork, router *SRouter, nets Subnets) (*SMeshNetworkMember, error) {
member := &SMeshNetworkMember{
MeshNetworkId: mn.Id,
RouterId: router.Id,
AdvertiseSubnets: nets.String(),
}
member.SetModelManager(man, member)
member.Name = fmt.Sprintf("%s-%s", mn.Name, router.Name)
man.TableSpec().Insert(member)
return member, nil
}
+157
View File
@@ -0,0 +1,157 @@
// Copyright 2019 Yunion
//
// 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.
package models
import (
"context"
"fmt"
"github.com/pkg/errors"
"yunion.io/x/jsonutils"
yerrors "yunion.io/x/pkg/util/errors"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
)
type SMeshNetwork struct {
db.SStandaloneResourceBase
}
type SMeshNetworkManager struct {
db.SStandaloneResourceBaseManager
}
var MeshNetworkManager *SMeshNetworkManager
func init() {
MeshNetworkManager = &SMeshNetworkManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SMeshNetwork{},
"meshnetworks_tbl",
"meshnetwork",
"meshnetworks",
),
}
MeshNetworkManager.SetVirtualObject(MeshNetworkManager)
}
func (mn *SMeshNetwork) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
var errs []error
if err := MeshNetworkMemberManager.removeByMeshNetwork(ctx, userCred, mn); err != nil {
errs = append(errs, err)
}
if err := IfaceManager.removeByMeshNetwork(ctx, userCred, mn); err != nil {
errs = append(errs, err)
}
if err := mn.SStandaloneResourceBase.CustomizeDelete(ctx, userCred, query, data); err != nil {
errs = append(errs, err)
}
return yerrors.NewAggregate(errs)
}
func (mn *SMeshNetwork) AllowPerformRealize(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, mn, "realize")
}
func (mn *SMeshNetwork) PerformRealize(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
members, err := MeshNetworkMemberManager.getMemebersByMeshNetwork(ctx, userCred, mn)
if err != nil {
return nil, httperrors.NewBadRequestError("fetch members: %v", err)
}
var errs []error
for i := range members {
member := &members[i]
router, err := member.getRouter()
if err != nil {
errs = append(errs, errors.WithMessagef(err, "get router %s", member.RouterId))
continue
}
if err := router.realize(ctx, userCred); err != nil {
errs = append(errs, errors.WithMessagef(err, "realize router %s", router.Name))
}
}
err = yerrors.NewAggregate(errs)
if err != nil {
err = httperrors.NewBadRequestError("some router realization failed: %s", err)
}
return nil, err
}
func (mn *SMeshNetwork) addRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter, nets Subnets) error {
// XXX lock
members, err := MeshNetworkMemberManager.getMemebersByMeshNetwork(ctx, userCred, mn)
if err != nil {
return err
}
for i := range members {
member := &members[i]
if member.RouterId == router.Id {
return fmt.Errorf("router %s is already a member of %s",
router.Name, mn.Name)
}
memberSubnets := member.subnetsParsed()
if _, p := memberSubnets.ContainsAnyEx(nets); p != nil {
return fmt.Errorf("router %s subnet %s already advertised by member %s(%s)",
router.Name, p.String(), member.Name, member.Id)
}
}
_, err = MeshNetworkMemberManager.addMember(ctx, userCred, mn, router, nets)
if err != nil {
return err
}
newIface, err := IfaceManager.addWireguardIface(ctx, userCred, router, mn)
if err != nil {
return err
}
// XXX allocate an iface and populate iface peers
var errs []error
for i := range members {
member := &members[i]
memberIface, err := IfaceManager.getByMeshNetworkMember(member)
if err != nil {
errs = append(errs, err)
continue
}
if err := memberIface.addOrUpdatePeer(ctx, userCred, newIface, nets, router); err != nil {
errs = append(errs, err)
}
if memberHost, err := RouterManager.getById(member.RouterId); err != nil {
errs = append(errs, err)
} else {
if err := newIface.addOrUpdatePeer(ctx, userCred, memberIface, member.subnetsParsed(), memberHost); err != nil {
errs = append(errs, err)
}
}
}
return yerrors.NewAggregate(errs)
}
func (mn *SMeshNetwork) removeRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
if err := MeshNetworkMemberManager.removeByMeshNetworkRouter(ctx, userCred, mn, router); err != nil {
return err
}
if err := IfaceManager.removeByMeshNetworkRouter(ctx, userCred, mn, router); err != nil {
return err
}
return nil
}
func (man *SMeshNetworkManager) removeRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
err := MeshNetworkMemberManager.removeByRouter(ctx, userCred, router)
return err
}
+295
View File
@@ -0,0 +1,295 @@
// Copyright 2019 Yunion
//
// 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.
package models
import (
"context"
"fmt"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/gotypes"
yerrors "yunion.io/x/pkg/util/errors"
"yunion.io/x/pkg/util/netutils"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
)
// Add revision?
type SRouter struct {
db.SStandaloneResourceBase
User string `nullable:"false" list:"user" update:"user" create:"optional"`
Host string `nullable:"false" list:"user" update:"user" create:"required"`
Port int `nullable:"false" list:"user" update:"user" create:"optional"`
PrivateKey string `nullable:"true" update:"user" create:"optional"` // do not allow get, list
RealizeWgIfaces bool `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"`
RealizeRoutes bool `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"`
RealizeRules bool `width:"16" charset:"ascii" nullable:"false" list:"user" create:"optional" update:"user"`
}
type SRouterManager struct {
db.SStandaloneResourceBaseManager
}
var RouterManager *SRouterManager
func init() {
RouterManager = &SRouterManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SRouter{},
"routers_tbl",
"router",
"routers",
),
}
RouterManager.SetVirtualObject(RouterManager)
}
func (man *SRouterManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
if _, err := man.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data); err != nil {
return nil, err
}
vs := []validators.IValidator{
validators.NewStringNonEmptyValidator("user").Default("cloudroot"),
validators.NewStringNonEmptyValidator("host"),
validators.NewPortValidator("port").Default(22),
validators.NewSSHKeyValidator("private_key").Optional(true),
validators.NewBoolValidator("realize_wg_ifaces").Default(true),
validators.NewBoolValidator("realize_routes").Default(true),
validators.NewBoolValidator("realize_rules").Default(true),
}
for _, v := range vs {
if err := v.Validate(data); err != nil {
return nil, err
}
}
// populate ssh credential through "cloudhost"
//
// if ! skip validation {
// ssh credential validation
// }
return data, nil
}
func (router *SRouter) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) {
err := RuleManager.addRouterRules(ctx, userCred, router)
if err != nil {
log.Errorf("add router rule: %v", err)
}
}
func (man *SRouterManager) getById(id string) (*SRouter, error) {
m, err := db.FetchById(man, id)
if err != nil {
return nil, err
}
router := m.(*SRouter)
return router, err
}
func (router *SRouter) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
if _, err := router.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data); err != nil {
return nil, err
}
vs := []validators.IValidator{
validators.NewStringNonEmptyValidator("user"),
validators.NewStringNonEmptyValidator("host"),
validators.NewPortValidator("port"),
validators.NewSSHKeyValidator("private_key").Optional(true),
validators.NewBoolValidator("realize_wg_ifaces"),
validators.NewBoolValidator("realize_routes"),
validators.NewBoolValidator("realize_rules"),
}
for _, v := range vs {
v.Optional(true)
if err := v.Validate(data); err != nil {
return nil, err
}
}
data.Set("_old_endpoint", jsonutils.NewString(router.endpointIP()))
return data, nil
}
func (router *SRouter) PostUpdate(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) {
endpointOld, _ := data.GetString("_old_endpoint")
endpoint := router.endpointIP()
if endpoint != endpointOld {
err := IfacePeerManager.updateEndpointIPByPeerRouter(ctx, endpoint, router)
if err != nil {
log.Errorf("updating peer endpoint failed: %v", err)
}
}
}
func (router *SRouter) CustomizeDelete(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) error {
var errs []error
if err := MeshNetworkManager.removeRouter(ctx, userCred, router); err != nil {
errs = append(errs, err)
}
if err := IfaceManager.removeByRouter(ctx, userCred, router); err != nil {
errs = append(errs, err)
}
if err := RuleManager.removeByRouter(ctx, userCred, router); err != nil {
errs = append(errs, err)
}
if err := router.SStandaloneResourceBase.CustomizeDelete(ctx, userCred, query, data); err != nil {
errs = append(errs, err)
}
return yerrors.NewAggregate(errs)
}
func (router *SRouter) AllowPerformJoinMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, router, "join-mesh-network")
}
func (router *SRouter) PerformJoinMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
mnV := validators.NewModelIdOrNameValidator("mesh_network", "meshnetwork", userCred)
advSubnetsV := validators.NewValidatorByActor("advertise_subnets",
validators.NewActorJoinedBy(",", validators.NewActorIPv4Prefix()))
{
vs := []validators.IValidator{
mnV,
advSubnetsV,
}
jd, ok := data.(*jsonutils.JSONDict)
if !ok {
return nil, httperrors.NewBadRequestError("expecting json dict")
}
for _, v := range vs {
if err := v.Validate(jd); err != nil {
return nil, err
}
}
}
// TODO dedup
nets := gotypes.ConvertSliceElemType(advSubnetsV.Value, (**netutils.IPV4Prefix)(nil)).([]*netutils.IPV4Prefix)
if len(nets) == 0 {
return nil, httperrors.NewBadRequestError("advertise_subnets must not be empty")
}
mn := mnV.Model.(*SMeshNetwork)
if err := mn.addRouter(ctx, userCred, router, nets); err != nil {
return nil, err
}
return data, nil
}
func (router *SRouter) AllowPerformLeaveMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, router, "leave-mesh-network")
}
func (router *SRouter) PerformLeaveMeshNetwork(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
jd, ok := data.(*jsonutils.JSONDict)
if !ok {
return nil, httperrors.NewBadRequestError("expecting json dict")
}
mnV := validators.NewModelIdOrNameValidator("mesh_network", "meshnetwork", userCred)
if err := mnV.Validate(jd); err != nil {
return nil, err
}
mn := mnV.Model.(*SMeshNetwork)
if err := mn.removeRouter(ctx, userCred, router); err != nil {
return nil, err
}
return nil, nil
}
func (router *SRouter) AllowPerformRegisterIfname(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, router, "register-ifname")
}
func (router *SRouter) PerformRegisterIfname(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
jd, ok := data.(*jsonutils.JSONDict)
if !ok {
return nil, httperrors.NewBadRequestError("expecting json dict")
}
ifnameV := validators.NewRegexpValidator("ifname", regexpIfname)
if err := ifnameV.Validate(jd); err != nil {
return nil, err
}
_, err := IfaceManager.addIface(ctx, userCred, router, ifnameV.Value)
if err != nil {
return nil, err
}
return nil, nil
}
func (router *SRouter) AllowPerformUnregisterIfname(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, router, "unregister-ifname")
}
func (router *SRouter) PerformUnregisterIfname(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
jd, ok := data.(*jsonutils.JSONDict)
if !ok {
return nil, httperrors.NewBadRequestError("expecting json dict")
}
ifname, err := jd.GetString("ifname")
if err != nil {
return nil, httperrors.NewBadRequestError("get request ifname field: %v", err)
}
iface, err := IfaceManager.getByRouterIfname(router, ifname)
if err != nil {
return nil, httperrors.NewBadRequestError("get iface: %s", err)
}
if iface.NetworkId != "" {
// XXX can_unregister
return nil, httperrors.NewBadRequestError("please use leave network to unregister")
}
if err := iface.remove(ctx, userCred); err != nil {
return nil, httperrors.NewBadRequestError("remove iface: %v", err)
}
return nil, nil
}
func (router *SRouter) mustFindFreePort(ctx context.Context) int {
// loop through ifaces listen port
ifaces, err := IfaceManager.getByRouter(router)
if err != nil {
panic(err)
}
for sport := 20000; sport < 65536; sport++ {
notfound := true
for i := range ifaces {
if ifaces[i].ListenPort == sport {
notfound = false
break
}
}
if notfound {
return sport
}
}
panic(fmt.Sprintf("cannot find free port for host %s(%s)", router.Name, router.Id))
}
func (router *SRouter) endpointIP() string {
return router.Host
}
func (router *SRouter) AllowPerformDeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, router, "deploy")
}
func (router *SRouter) PerformDeploy(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
if err := RouterDeploymentManager.requestDeployment(ctx, userCred, router); err != nil {
return nil, err
}
return nil, nil
}
+367
View File
@@ -0,0 +1,367 @@
// Copyright 2019 Yunion
//
// 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.
package models
import (
"encoding/json"
"encoding/xml"
"fmt"
"strings"
"github.com/pkg/errors"
"yunion.io/x/onecloud/pkg/util/ansiblev2"
)
func (router *SRouter) ansibleHost() (*ansiblev2.Host, error) {
vars := map[string]interface{}{
"ansible_user": router.User,
"ansible_host": router.Host,
}
if router.User != "root" {
vars["ansible_become"] = "yes"
}
if router.PrivateKey != "" {
vars["ansible_ssh_private_key_file"] = ".id_rsa"
}
if router.RealizeWgIfaces {
if err := router.inventoryWireguardVars(vars); err != nil {
return nil, err
}
}
h := ansiblev2.NewHost()
h.Vars = vars
return h, nil
}
func (router *SRouter) playFiles() map[string]string {
r := map[string]string{
"wgX.conf.j2": wgX_conf_j2,
}
if router.PrivateKey != "" {
r[".id_rsa"] = router.PrivateKey
}
return r
}
func (router *SRouter) playFilesStr() string {
files := router.playFiles()
r, _ := json.Marshal(files)
return string(r)
}
func (router *SRouter) inventoryWireguardVars(vars map[string]interface{}) error {
type (
WgNetworks []string
WgInterface map[string]interface{}
WgPeer map[string]interface{}
WgPeers map[string]WgPeer
)
ifaces, err := IfaceManager.getByRouter(router)
if err != nil {
return err
}
wgnetworks := WgNetworks{}
for i := range ifaces {
iface := &ifaces[i]
if iface.PrivateKey == "" {
continue
}
ifacePeers, err := IfacePeerManager.getByIface(iface)
if err != nil {
return err
}
wgpeers := WgPeers{}
for j := range ifacePeers {
ifacePeer := &ifacePeers[j]
if ifacePeer.PublicKey == "" {
continue
}
wgpeer := WgPeer{
"public_key": ifacePeer.PublicKey,
"allowed_ips": ifacePeer.AllowedIPs,
"endpoint": ifacePeer.Endpoint,
}
if ifacePeer.PersistentKeepalive > 0 {
wgpeer["persistent_keepalive"] = ifacePeer.PersistentKeepalive
}
wgpeers[ifacePeer.Name] = wgpeer
}
if len(wgpeers) == 0 {
continue
}
vars["wireguard_"+iface.Ifname+"_interface"] = WgInterface{
"private_key": iface.PrivateKey,
"listen_port": iface.ListenPort,
}
vars["wireguard_"+iface.Ifname+"_peers"] = wgpeers
wgnetworks = append(wgnetworks, iface.Ifname)
}
vars["wireguard_networks"] = wgnetworks
return nil
}
func (router *SRouter) playInstallWireguard() *ansiblev2.Play {
play := ansiblev2.NewPlay(
&ansiblev2.Task{
Name: "Enable EPEL",
ModuleName: "package",
ModuleArgs: map[string]interface{}{
"name": "epel-release",
"state": "present",
},
},
&ansiblev2.Task{
Name: "Check existence of wireguard repo file",
ModuleName: "stat",
ModuleArgs: map[string]interface{}{
"path": "/etc/yum.repos.d/_copr_jdoss-wireguard.repo",
},
Register: "wireguard_repo",
},
&ansiblev2.Task{
Name: "Enable wireguard repo from copr",
ModuleName: "get_url",
ModuleArgs: map[string]interface{}{
"dest": "/etc/yum.repos.d/_copr_jdoss-wireguard.repo",
"url": "https://copr.fedorainfracloud.org/coprs/jdoss/wireguard/repo/epel-7/jdoss-wireguard-epel-7.repo",
},
When: "(not wireguard_repo.stat.exists) or (wireguard_repo.stat.size < 10)",
},
&ansiblev2.Task{
Name: "Install wireguard packages",
ModuleName: "package",
ModuleArgs: map[string]interface{}{
"name": "{{ item }}",
"state": "present",
},
WithPlugin: "items",
WithPluginVal: []string{"wireguard-dkms", "wireguard-tools"},
},
&ansiblev2.Task{
Name: "Create /etc/wireguard",
ModuleName: "file",
ModuleArgs: map[string]interface{}{
"path": "/etc/wireguard",
"state": "directory",
"owner": "root",
"group": "root",
},
},
)
play.Hosts = "all"
play.Name = "Install WireGuard"
return play
}
func (router *SRouter) playDeployWireguardNetworks() *ansiblev2.Play {
play := ansiblev2.NewPlay(
&ansiblev2.ShellTask{
Name: "List existing managed wireguard networks",
Script: `grep -rnl 'Ansible managed' /etc/wireguard/ | grep '\.conf$' | cut -d/ -f4 | cut -d. -f1`,
Register: "oldconfs",
IgnoreErrors: true,
},
&ansiblev2.Task{
Name: "Backup stale wireguard confs",
ModuleName: "copy",
ModuleArgs: map[string]interface{}{
"src": "/etc/wireguard/{{ item }}.conf",
"dest": "/etc/wireguard/{{ item }}.conf.stale",
"remote_src": "yes",
},
WithPlugin: "items",
WithPluginVal: "{{ oldconfs.stdout_lines }}",
When: "(not oldconfs.failed) and (item not in wireguard_networks)",
},
&ansiblev2.Task{
Name: "Remove stale wireguard confs",
ModuleName: "file",
ModuleArgs: map[string]interface{}{
"path": "/etc/wireguard/{{ item }}.conf",
"state": "absent",
},
WithPlugin: "items",
WithPluginVal: "{{ oldconfs.stdout_lines }}",
When: "(not oldconfs.failed) and (item not in wireguard_networks)",
},
&ansiblev2.Task{
Name: "Disable stale wireguard networks",
ModuleName: "service",
ModuleArgs: map[string]interface{}{
"name": "wg-quick@{{ item }}",
"state": "stopped",
"enabled": "no",
},
WithPlugin: "items",
WithPluginVal: "{{ oldconfs.stdout_lines }}",
When: "(not oldconfs.failed) and (item not in wireguard_networks)",
},
&ansiblev2.Task{
Name: "Configure wireguard conf",
ModuleName: "template",
ModuleArgs: map[string]interface{}{
"src": "wgX.conf.j2", // wgX_conf_j2
"dest": "/etc/wireguard/{{ item }}.conf",
"mode": 0600,
},
WithPlugin: "items",
WithPluginVal: "{{ wireguard_networks }}",
Register: "configuration",
},
&ansiblev2.Task{
Name: "Enable wg-quick@xx service",
ModuleName: "service",
ModuleArgs: map[string]interface{}{
"name": "wg-quick@{{ item }}",
"enabled": "yes",
},
WithPlugin: "items",
WithPluginVal: "{{ wireguard_networks }}",
},
&ansiblev2.Task{
Name: "Restart wg-quick@xx service",
ModuleName: "service",
ModuleArgs: map[string]interface{}{
"name": "wg-quick@{{ item.1 }}",
"state": "restarted",
},
WithPlugin: "indexed_items",
WithPluginVal: "{{ wireguard_networks }}",
When: "configuration.results[item.0].changed",
},
)
play.Hosts = "all"
play.Name = "Configure wireguard networks"
return play
}
func (router *SRouter) playDeployRoutes() (*ansiblev2.Play, error) {
r, err := RouteManager.routeLinesRouter(router)
if err != nil {
return nil, err
}
tasks := []ansiblev2.ITask{}
i := 0
for ifname, lines := range r {
if len(lines) == 0 {
continue
}
iface, err := IfaceManager.getByRouterIfname(router, ifname)
if err != nil {
return nil, errors.WithMessagef(err, "get iface %s", ifname)
}
filename := "route-" + ifname
content := strings.Join(lines, "\n") + "\n"
registerVar := fmt.Sprintf("var%d", i)
i += 1
tasks = append(tasks, &ansiblev2.Task{
Name: "Put routes for " + ifname,
ModuleName: "copy",
ModuleArgs: map[string]interface{}{
"content": content,
"dest": "/etc/sysconfig/network-scripts/" + filename,
"owner": "root",
"group": "root",
"mode": "0644",
},
Register: registerVar,
})
if !iface.isTypeWireguard() {
tasks = append(tasks, &ansiblev2.ShellTask{
Name: fmt.Sprintf("Apply routes (Ifup/ifdown %s)", ifname),
Script: fmt.Sprintf("ifdown %s; ifup %s", ifname, ifname),
IgnoreErrors: true,
When: fmt.Sprintf("%s.changed", registerVar),
})
}
// apply by diff on changed
}
play := ansiblev2.NewPlay(tasks...)
play.Hosts = "all"
play.Name = "Configure routes"
return play, nil
}
func (router *SRouter) playDeployRules() (*ansiblev2.Play, error) {
d, err := RuleManager.firewalldDirectByRouter(router)
if err != nil {
return nil, err
}
directXML, err := xml.MarshalIndent(d, "", " ")
if err != nil {
return nil, err
}
play := ansiblev2.NewPlay(
&ansiblev2.Task{
Name: "Install firewalld",
ModuleName: "package",
ModuleArgs: map[string]interface{}{
"name": "firewalld",
"state": "present",
},
},
&ansiblev2.Task{
Name: "Enable firewalld",
ModuleName: "service",
ModuleArgs: map[string]interface{}{
"name": "firewalld",
"enabled": "yes",
},
},
&ansiblev2.Task{
Name: "Put firewalld direct.xml",
ModuleName: "copy",
ModuleArgs: map[string]interface{}{
"content": string(directXML),
"dest": "/etc/firewalld/direct.xml",
"owner": "root",
"group": "root",
"mode": "0644",
},
Register: "direct_xml",
},
&ansiblev2.Task{
Name: "Restart firewalld",
ModuleName: "service",
ModuleArgs: map[string]interface{}{
"name": "firewalld",
"state": "restarted",
},
When: "direct_xml.changed",
},
)
play.Hosts = "all"
play.Name = "Configure firewall rules"
return play, nil
}
func (router *SRouter) playEssential() *ansiblev2.Play {
play := ansiblev2.NewPlay(
&ansiblev2.Task{
Name: "Enable ip_forward",
ModuleName: "sysctl",
ModuleArgs: map[string]interface{}{
"name": "net.ipv4.ip_forward",
"value": "1",
"state": "present",
"reload": "yes",
},
},
)
play.Hosts = "all"
play.Name = "Perform essential steps"
return play
}
@@ -0,0 +1,68 @@
// Copyright 2019 Yunion
//
// 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.
package models
const wgX_conf_j2 = `
{% set interface = lookup('vars', 'wireguard_' + item + '_interface') -%}
{% set peers = lookup('vars', 'wireguard_' + item + '_peers') -%}
{% set interface_required_keys = { 'private_key': 'PrivateKey' } -%}
{% set interface_optional_keys = {
'address': 'Address',
'listen_port': 'ListenPort',
'fw_mark': 'FwMark',
'dns': 'DNS',
'mtu': 'MTU',
'table': 'Table',
'pre_up': 'PreUp',
'post_up': 'PostUp',
'pre_down': 'PreDown',
'post_down': 'PostDown',
'save_config': 'SaveConfig'
} -%}
{% set peer_required_keys = {
'public_key': 'PublicKey',
'allowed_ips': 'AllowedIPs'
} -%}
{% set peer_optional_keys = {
'endpoint': 'EndPoint',
'preshared_key': 'PresharedKey',
'persistent_keepalive': 'PersistentKeepalive'
} -%}
{{ ansible_managed | comment }}
[Interface]
{% for key, option in interface_required_keys.items() %}
{{ option }} = {{ interface[key] }}
{% endfor %}
{% for key, option in interface_optional_keys.items() %}
{% if interface[key] is defined %}
{{ option }} = {{ interface[key] }}
{% endif %}
{% endfor %}
{% for peer_name, peer in peers.items() %}
[Peer] # {{ peer_name }}
{% for key, option in peer_required_keys.items() %}
{{ option }} = {{ peer[key] }}
{% endfor %}
{% for key, option in peer_optional_keys.items() %}
{% if peer[key] is defined %}
{{ option }} = {{ peer[key] }}
{% endif %}
{% endfor %}
{% endfor %}
`
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2019 Yunion
//
// 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.
package models
import (
"context"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/mcclient"
)
type SRouterDeployment struct {
db.SStandaloneResourceBase
RouterId string
AnsiblePlaybookId string
RouterRevision int
}
type SRouterDeploymentManager struct {
db.SStandaloneResourceBaseManager
}
var RouterDeploymentManager *SRouterDeploymentManager
func init() {
RouterDeploymentManager = &SRouterDeploymentManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SRouterDeployment{},
"router_deployments_tbl",
"router_deployment",
"router_deployments",
),
}
RouterDeploymentManager.SetVirtualObject(RouterDeploymentManager)
}
func (man *SRouterDeploymentManager) requestDeployment(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
// XXX
// make inventory
// make playbook
// queue a deploy ansible task
return nil
}
+93
View File
@@ -0,0 +1,93 @@
// Copyright 2019 Yunion
//
// 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.
package models
import (
"context"
"fmt"
"github.com/pkg/errors"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/mcclient/auth"
mcclient_modules "yunion.io/x/onecloud/pkg/mcclient/modules"
"yunion.io/x/onecloud/pkg/util/ansiblev2"
"yunion.io/x/onecloud/pkg/util/rand"
)
func (router *SRouter) realize(ctx context.Context, userCred mcclient.TokenCredential) error {
plays := []*ansiblev2.Play{
router.playEssential(),
}
host, err := router.ansibleHost()
if err != nil {
return err
}
if router.RealizeWgIfaces {
plays = append(plays,
router.playInstallWireguard(),
router.playDeployWireguardNetworks(),
)
}
if router.RealizeRoutes {
playRoutes, err := router.playDeployRoutes()
if err != nil {
return err
}
plays = append(plays, playRoutes)
}
if router.RealizeRules {
playRules, err := router.playDeployRules()
if err != nil {
return err
}
plays = append(plays, playRules)
}
inv := ansiblev2.NewInventory()
inv.SetHost(router.Name, host)
pb := ansiblev2.NewPlaybook(plays...)
files := router.playFilesStr()
params := jsonutils.NewDict()
params.Set("creator_mark", jsonutils.NewString("router:"+router.Id))
params.Set("name", jsonutils.NewString(router.Name+"-"+fmt.Sprintf("%d-", router.UpdateVersion)+rand.String(5)))
params.Set("inventory", jsonutils.NewString(inv.String()))
params.Set("playbook", jsonutils.NewString(pb.String()))
params.Set("files", jsonutils.NewString(files))
cliSess := auth.GetSession(ctx, userCred, "", "")
if _, err := mcclient_modules.AnsiblePlaybooksV2.Create(cliSess, params); err != nil {
return errors.WithMessagef(err, "create ansible task")
}
return nil
}
func (router *SRouter) AllowPerformRealize(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) bool {
return db.IsAdminAllowPerform(userCred, router, "realize")
}
func (router *SRouter) PerformRealize(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) {
err := router.realize(ctx, userCred)
if err != nil {
return nil, httperrors.NewBadRequestError("%s", err)
}
return nil, nil
}
+260
View File
@@ -0,0 +1,260 @@
// Copyright 2019 Yunion
//
// 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.
package models
import (
"context"
"fmt"
"yunion.io/x/jsonutils"
yerrors "yunion.io/x/pkg/util/errors"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/rand"
)
type SRoute struct {
db.SStandaloneResourceBase
IfaceId string `length:"32" nullable:"false" list:"user" create:"required"`
Ifname string `length:"32" nullable:"false" list:"user" create:"optional"`
Network string `length:"32" nullable:"false" list:"user" update:"user" create:"required"`
Gateway string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
RouterId string `length:"32" nullable:"false" list:"user" create:"optional"`
}
type SRouteManager struct {
db.SStandaloneResourceBaseManager
}
var RouteManager *SRouteManager
func init() {
RouteManager = &SRouteManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SRoute{},
"routes_tbl",
"route",
"routes",
),
}
RouteManager.SetVirtualObject(RouteManager)
}
func (man *SRouteManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
if _, err := man.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data); err != nil {
return nil, err
}
ifaceV := validators.NewModelIdOrNameValidator("iface", "iface", ownerId)
networkV := validators.NewIPv4PrefixValidator("network")
gatewayV := validators.NewIPv4AddrValidator("gateway")
vs := []validators.IValidator{
networkV,
gatewayV.Optional(true),
ifaceV,
}
for _, v := range vs {
if err := v.Validate(data); err != nil {
return nil, err
}
}
iface := ifaceV.Model.(*SIface)
network := networkV.Value.String()
routerId := iface.RouterId
{
if routes, err := man.getByFilter(map[string]string{
"router_id": routerId,
"network": network,
}); err != nil {
return nil, httperrors.NewConflictError("query existing route to network %s: %v", network, err)
} else if len(routes) > 0 {
return nil, httperrors.NewConflictError("route to %s already exist: %s(%s)", network, routes[0].Name, routes[0].Id)
}
}
data.Set("router_id", jsonutils.NewString(routerId))
data.Set("ifname", jsonutils.NewString(iface.Ifname))
routerV := validators.NewModelIdOrNameValidator("router", "router", ownerId)
if err := routerV.Validate(data); err != nil {
return nil, err
}
if !data.Contains("name") {
router := routerV.Model.(*SRouter)
data.Set("name", jsonutils.NewString(
router.Name+"-"+iface.Name+"-"+rand.String(4)),
)
}
return nil, nil
}
func (man *SRouteManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
q, err := man.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query)
if err != nil {
return nil, err
}
data := query.(*jsonutils.JSONDict)
q, err = validators.ApplyModelFilters(q, data, []*validators.ModelFilterOptions{
{Key: "router", ModelKeyword: "router", OwnerId: userCred},
{Key: "iface", ModelKeyword: "iface", OwnerId: userCred},
})
if err != nil {
return nil, err
}
return q, nil
}
func (route *SRoute) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
if _, err := route.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data); err != nil {
return nil, err
}
vs := []validators.IValidator{
validators.NewIPv4PrefixValidator("network"),
validators.NewIPv4AddrValidator("gateway"),
}
for _, v := range vs {
v.Optional(true)
if err := v.Validate(data); err != nil {
return nil, err
}
}
return nil, nil
}
func (man *SRouteManager) removeByIface(ctx context.Context, userCred mcclient.TokenCredential, iface *SIface) error {
routes := []SRoute{}
q := man.Query().Equals("iface_id", iface.Id)
if err := db.FetchModelObjects(RouteManager, q, &routes); err != nil {
return err
}
var errs []error
for j := range routes {
if err := routes[j].Delete(ctx, userCred); err != nil {
errs = append(errs, err)
}
}
return yerrors.NewAggregate(errs)
}
func (man *SRouteManager) getByFilter(filter map[string]string) ([]SRoute, error) {
routes := []SRoute{}
q := man.Query()
for key, val := range filter {
q = q.Equals(key, val)
}
if err := db.FetchModelObjects(RouteManager, q, &routes); err != nil {
return nil, err
}
return routes, nil
}
func (man *SRouteManager) getOneByFilter(filter map[string]string) (*SRoute, error) {
routes, err := man.getByFilter(filter)
if err != nil {
return nil, err
}
if len(routes) == 0 {
return nil, errNotFound(fmt.Errorf("cannot find iface route: %#v", filter))
}
if len(routes) > 1 {
return nil, errMoreThanOne(fmt.Errorf("found more than 1 iface routes: %#v", filter))
}
return &routes[0], nil
}
func (man *SRouteManager) checkExistenceByFilter(filter map[string]string) error {
ifaces, err := man.getByFilter(filter)
if err != nil {
return err
}
if len(ifaces) > 0 {
return fmt.Errorf("iface exist: %s(%s)", ifaces[0].Name, ifaces[0].Id)
}
return nil
}
func (man *SRouteManager) getByIface(iface *SIface) ([]SRoute, error) {
filter := map[string]string{
"router_id": iface.RouterId,
"iface_id": iface.Id,
}
routes, err := man.getByFilter(filter)
if err != nil {
return nil, err
}
return routes, nil
}
func (man *SRouteManager) getByRouter(router *SRouter) ([]SRoute, error) {
filter := map[string]string{
"router_id": router.Id,
}
routes, err := man.getByFilter(filter)
if err != nil {
return nil, err
}
return routes, nil
}
func (route *SRoute) routeLine() string {
line := route.Network
if route.Gateway != "" {
line += " via " + route.Gateway
}
if route.Ifname != "" {
line += " dev " + route.Ifname
}
return line
}
func (man *SRouteManager) routeLinesByIface(iface *SIface) ([]string, error) {
routes, err := man.getByIface(iface)
if err != nil {
return nil, err
}
lines := []string{}
for i := range routes {
route := &routes[i]
line := route.routeLine()
if line != "" {
lines = append(lines, line)
}
}
return lines, nil
}
func (man *SRouteManager) routeLinesRouter(router *SRouter) (map[string][]string, error) {
routes, err := man.getByRouter(router)
if err != nil {
return nil, err
}
r := map[string][]string{}
for i := range routes {
route := &routes[i]
line := route.routeLine()
if line != "" {
lines := r[route.Ifname]
lines = append(lines, line)
r[route.Ifname] = lines
}
}
return r, nil
}
+459
View File
@@ -0,0 +1,459 @@
// Copyright 2019 Yunion
//
// 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.
package models
import (
"context"
"fmt"
"strings"
"yunion.io/x/jsonutils"
yerrors "yunion.io/x/pkg/util/errors"
"yunion.io/x/sqlchemy"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudcommon/validators"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/mcclient"
"yunion.io/x/onecloud/pkg/util/choices"
"yunion.io/x/onecloud/pkg/util/firewalld"
"yunion.io/x/onecloud/pkg/util/rand"
)
type SRule struct {
db.SStandaloneResourceBase
Prio int `nullable:"false" list:"user" update:"user" create:"optional"`
MatchSrcNet string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
MatchDestNet string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
MatchProto string `length:"8" nullable:"false" list:"user" update:"user" create:"optional"`
MatchSrcPort int `nullable:"false" list:"user" update:"user" create:"optional"`
MatchDestPort int `nullable:"false" list:"user" update:"user" create:"optional"`
MatchInIfname string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
MatchOutIfname string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
Action string `length:"32" nullable:"false" list:"user" update:"user" create:"required"`
ActionOptions string `length:"32" nullable:"false" list:"user" update:"user" create:"optional"`
RouterId string `length:"32" nullable:"false" list:"user" create:"optional"`
IsSystem bool `nullable:"false" list:"user" create:"optional"`
}
const (
MIN_PRIO = 0
MAX_PRIO = 2000
DEF_PRIO = 0
DEF_PRIO_ROUTER_FORWARD = 1000
DEF_PRIO_MASQUERADE = 1000
ACT_SNAT = "SNAT"
ACT_DNAT = "DNAT"
ACT_MASQUERADE = "MASQUERADE"
ACT_TCPMSS = "TCPMSS" // FORWARD chain for now
ACT_INPUT_ACCEPT = "INPUT_ACCEPT"
ACT_FORWARD_ACCEPT = "FORWARD_ACCEPT"
PROTO_TCP = "tcp"
PROTO_UDP = "udp"
)
var (
actionChoices = choices.NewChoices(
ACT_SNAT,
ACT_DNAT,
ACT_MASQUERADE,
ACT_TCPMSS,
//"DROP",
ACT_INPUT_ACCEPT,
ACT_FORWARD_ACCEPT,
//"REJECT",
)
protoChoices = choices.NewChoices(
PROTO_TCP,
PROTO_UDP,
)
)
type SRuleManager struct {
db.SStandaloneResourceBaseManager
}
var RuleManager *SRuleManager
func init() {
RuleManager = &SRuleManager{
SStandaloneResourceBaseManager: db.NewStandaloneResourceBaseManager(
SRule{},
"rules_tbl",
"rule",
"rules",
),
}
RuleManager.SetVirtualObject(RuleManager)
}
func (man *SRuleManager) validateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict, rule *SRule) error {
isUpdate := rule != nil
routerV := validators.NewModelIdOrNameValidator("router", "router", ownerId)
inIfnameV := validators.NewRegexpValidator("match_in_ifname", regexpIfname)
outIfnameV := validators.NewRegexpValidator("match_out_ifname", regexpIfname)
protoV := validators.NewStringChoicesValidator("match_proto", protoChoices)
srcPortV := validators.NewPortValidator("match_src_port")
destPortV := validators.NewPortValidator("match_dest_port")
actionV := validators.NewStringChoicesValidator("action", protoChoices)
actionOptsV := validators.NewStringLenRangeValidator("action_options", 0, 256)
if isUpdate {
inIfnameV.Default(rule.MatchInIfname)
outIfnameV.Default(rule.MatchOutIfname)
protoV.Default(rule.MatchProto)
srcPortV.Default(int64(rule.MatchSrcPort))
destPortV.Default(int64(rule.MatchDestPort))
actionV.Default(rule.Action)
actionOptsV.Default(rule.ActionOptions)
}
vs := []validators.IValidator{
inIfnameV.Optional(true),
outIfnameV.Optional(true),
validators.NewIPv4PrefixValidator("match_src_net").Optional(true),
validators.NewIPv4PrefixValidator("match_dest_net").Optional(true),
protoV.Optional(true),
srcPortV.Optional(true),
destPortV.Optional(true),
actionV,
actionOptsV.Optional(true),
routerV,
}
for _, v := range vs {
if isUpdate {
v.Optional(true)
}
if err := v.Validate(data); err != nil {
return err
}
}
if actionV.Value == ACT_TCPMSS {
if protoV.Value != "" && protoV.Value != PROTO_TCP {
return httperrors.NewBadRequestError("TCPMSS only works for proto tcp")
}
if protoV.Value == "" {
data.Set("match_proto", jsonutils.NewString(PROTO_TCP))
}
} else if actionV.Value == ACT_DNAT {
if outIfnameV.Value != "" {
return httperrors.NewBadRequestError("cannot match out interface for DNAT")
}
} else if actionV.Value == ACT_SNAT {
if inIfnameV.Value != "" {
return httperrors.NewBadRequestError("cannot match in interface for SNAT")
}
}
if (srcPortV.Value > 0 || destPortV.Value > 0) && protoV.Value == "" {
return httperrors.NewBadRequestError("protocol must be specified when matching port")
}
{
prioDefault := int64(0)
if !isUpdate && actionV.Value == ACT_MASQUERADE && !data.Contains("prio") {
prioDefault = DEF_PRIO_MASQUERADE
}
prioV := validators.NewRangeValidator("prio", MIN_PRIO, MAX_PRIO)
if !isUpdate {
prioV.Default(prioDefault)
}
if err := prioV.Validate(data); err != nil {
return err
}
}
// XXX validate interface against db
// XXX validate action options
if !isUpdate && !data.Contains("name") {
router := routerV.Model.(*SRouter)
data.Set("name", jsonutils.NewString(
router.Name+"-"+rand.String(4),
))
}
return nil
}
func (man *SRuleManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
if _, err := man.SStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data); err != nil {
return nil, err
}
if err := man.validateData(ctx, userCred, ownerId, query, data, nil); err != nil {
return nil, err
}
return nil, nil
}
func (man *SRuleManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) {
q, err := man.SStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query)
if err != nil {
return nil, err
}
data := query.(*jsonutils.JSONDict)
q, err = validators.ApplyModelFilters(q, data, []*validators.ModelFilterOptions{
{Key: "router", ModelKeyword: "router", OwnerId: userCred},
})
if err != nil {
return nil, err
}
return q, nil
}
func (rule *SRule) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) {
if _, err := rule.SStandaloneResourceBase.ValidateUpdateData(ctx, userCred, query, data); err != nil {
return nil, err
}
if err := RuleManager.validateData(ctx, userCred, rule.GetOwnerId(), query, data, rule); err != nil {
return nil, err
}
return nil, nil
}
func (man *SRuleManager) removeByRouter(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
rules, err := man.getByFilter(map[string]string{
"router_id": router.Id,
})
if err != nil {
return err
}
var errs []error
for j := range rules {
if err := rules[j].Delete(ctx, userCred); err != nil {
errs = append(errs, err)
}
}
return yerrors.NewAggregate(errs)
}
func (man *SRuleManager) getByFilter(filter map[string]string) ([]SRule, error) {
rules := []SRule{}
q := man.Query()
for key, val := range filter {
q = q.Equals(key, val)
}
if err := db.FetchModelObjects(RuleManager, q, &rules); err != nil {
return nil, err
}
return rules, nil
}
func (man *SRuleManager) getOneByFilter(filter map[string]string) (*SRule, error) {
rules, err := man.getByFilter(filter)
if err != nil {
return nil, err
}
if len(rules) == 0 {
return nil, errNotFound(fmt.Errorf("cannot find rule: %#v", filter))
}
if len(rules) > 1 {
return nil, errMoreThanOne(fmt.Errorf("found more than 1 rules: %#v", filter))
}
return &rules[0], nil
}
func (man *SRuleManager) checkExistenceByFilter(filter map[string]string) error {
rules, err := man.getByFilter(filter)
if err != nil {
return err
}
if len(rules) > 0 {
return fmt.Errorf("rule exist: %s(%s)", rules[0].Name, rules[0].Id)
}
return nil
}
func (man *SRuleManager) getByRouter(router *SRouter) ([]SRule, error) {
filter := map[string]string{
"router_id": router.Id,
}
rules, err := man.getByFilter(filter)
if err != nil {
return nil, err
}
return rules, nil
}
func (man *SRuleManager) addRouterRules(ctx context.Context, userCred mcclient.TokenCredential, router *SRouter) error {
r := &SRule{
Prio: DEF_PRIO_ROUTER_FORWARD,
RouterId: router.Id,
Action: ACT_FORWARD_ACCEPT,
}
r.IsSystem = true
r.Name = router.Name + "-allow-forward-" + rand.String(4)
r.SetModelManager(man, r)
err := man.addRule(ctx, userCred, r)
return err
}
func (man *SRuleManager) addWireguardIfaceRules(ctx context.Context, userCred mcclient.TokenCredential, iface *SIface) error {
r := &SRule{
RouterId: iface.RouterId,
MatchProto: PROTO_UDP,
MatchDestPort: iface.ListenPort,
Action: ACT_INPUT_ACCEPT,
}
r.IsSystem = true
r.Name = iface.Name + "-allow-" + fmt.Sprintf("%d-", iface.ListenPort) + rand.String(4)
r.SetModelManager(man, r)
rules := man.ifaceTCPMSSRules(ctx, userCred, iface)
rules = append(rules, r)
err := man.addRules(ctx, userCred, rules)
return err
}
func (man *SRuleManager) ifaceTCPMSSRules(ctx context.Context, userCred mcclient.TokenCredential, iface *SIface) []*SRule {
rules := []*SRule{
&SRule{
RouterId: iface.RouterId,
MatchInIfname: iface.Ifname,
MatchProto: PROTO_TCP,
Action: ACT_TCPMSS,
ActionOptions: "--clamp-mss-to-pmtu",
},
&SRule{
RouterId: iface.RouterId,
MatchProto: PROTO_TCP,
MatchOutIfname: iface.Ifname,
Action: ACT_TCPMSS,
ActionOptions: "--clamp-mss-to-pmtu",
},
}
for _, r := range rules {
r.IsSystem = true
r.Name = iface.Name + "-tcpmss-" + rand.String(4)
r.SetModelManager(man, r)
}
return rules
}
func (man *SRuleManager) addRules(ctx context.Context, userCred mcclient.TokenCredential, rules []*SRule) error {
errs := []error{}
for _, r := range rules {
err := man.addRule(ctx, userCred, r)
if err != nil {
errs = append(errs, err)
continue
}
}
return yerrors.NewAggregate(errs)
}
func (man *SRuleManager) addRule(ctx context.Context, userCred mcclient.TokenCredential, rule *SRule) error {
return man.TableSpec().Insert(rule)
}
func (rule *SRule) firewalldRule() (*firewalld.Rule, error) {
var (
prio int
table string
chain string
action string
body string
matchOthers []string
actionOthers []string
)
prio = rule.Prio
switch rule.Action {
case ACT_SNAT, ACT_DNAT, ACT_MASQUERADE:
table = "nat"
if rule.Action == ACT_DNAT {
chain = "PREROUTING"
} else {
chain = "POSTROUTING"
}
action = rule.Action
case ACT_TCPMSS:
table = "mangle"
chain = "FORWARD" // save INPUT, OUTPUT for future occasions
action = rule.Action
matchOthers = []string{"-m", "tcp", "--tcp-flags", "SYN,RST", "SYN"}
if rule.ActionOptions == "" {
actionOthers = []string{"--clamp-mss-to-pmtu"}
}
case ACT_INPUT_ACCEPT:
table = "filter"
chain = "INPUT"
action = "ACCEPT"
case ACT_FORWARD_ACCEPT:
table = "filter"
chain = "FORWARD"
action = "ACCEPT"
default:
return nil, fmt.Errorf("unknown rule action: %s", rule.Action)
}
{
elms := []string{}
if rule.MatchInIfname != "" {
elms = append(elms, "-i", rule.MatchInIfname)
}
if rule.MatchOutIfname != "" {
elms = append(elms, "-o", rule.MatchOutIfname)
}
if rule.MatchSrcNet != "" {
elms = append(elms, "-s", rule.MatchSrcNet)
}
if rule.MatchDestNet != "" {
elms = append(elms, "-d", rule.MatchDestNet)
}
if rule.MatchProto != "" {
elms = append(elms, "-p", rule.MatchProto)
}
if rule.MatchSrcPort > 0 {
elms = append(elms, "--sport", fmt.Sprintf("%d", rule.MatchSrcPort))
}
if rule.MatchDestPort > 0 {
elms = append(elms, "--dport", fmt.Sprintf("%d", rule.MatchDestPort))
}
elms = append(elms, matchOthers...) // XXX empty elm
elms = append(elms, "-j", action)
if rule.ActionOptions != "" {
elms = append(elms, rule.ActionOptions)
}
elms = append(elms, actionOthers...)
body = strings.Join(elms, " ")
}
r := firewalld.NewIP4Rule(prio, table, chain, body)
return r, nil
}
func (man *SRuleManager) firewalldDirectByRouter(router *SRouter) (*firewalld.Direct, error) {
rules, err := man.getByRouter(router)
if err != nil {
return nil, err
}
rs := []*firewalld.Rule{}
errs := []error{}
for i := range rules {
rule := &rules[i]
r, err := rule.firewalldRule()
if err != nil {
errs = append(errs, err)
continue
}
rs = append(rs, r)
}
return firewalld.NewDirect(rs...), yerrors.NewAggregate(errs)
}
+16
View File
@@ -0,0 +1,16 @@
package models
type (
errNotFound error
errMoreThanOne error
)
func IsNotFound(err error) bool {
_, ok := err.(errNotFound)
return ok
}
func IsMoreThanOne(err error) bool {
_, ok := err.(errMoreThanOne)
return ok
}
+38
View File
@@ -0,0 +1,38 @@
package models
import (
"strings"
"yunion.io/x/pkg/util/netutils"
)
type Subnets []*netutils.IPV4Prefix
func (nets Subnets) StrList() []string {
r := make([]string, 0, len(nets))
for _, p := range nets {
r = append(r, p.String())
}
return r
}
func (nets Subnets) String() string {
r := nets.StrList()
return strings.Join(r, ",")
}
func (nets Subnets) ContainsAny(nets1 Subnets) bool {
contains, _ := nets.ContainsAnyEx(nets1)
return contains
}
func (nets Subnets) ContainsAnyEx(nets1 Subnets) (bool, *netutils.IPV4Prefix) {
for _, p0 := range nets {
for _, p1 := range nets1 {
if p0.Equals(p1) {
return true, p0
}
}
}
return false, nil
}
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2019 Yunion
//
// 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.
package options // import "yunion.io/x/onecloud/pkg/cloudnet/options"
+26
View File
@@ -0,0 +1,26 @@
// Copyright 2019 Yunion
//
// 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.
package options
import common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
type CloudnetOptions struct {
common_options.CommonOptions
common_options.DBOptions
}
var (
Options CloudnetOptions
)
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2019 Yunion
//
// 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.
package service // import "yunion.io/x/onecloud/pkg/cloudnet/service"
+57
View File
@@ -0,0 +1,57 @@
// Copyright 2019 Yunion
//
// 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.
package service
// Copyright 2019 Yunion
//
// 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 (
"yunion.io/x/onecloud/pkg/appsrv"
"yunion.io/x/onecloud/pkg/appsrv/dispatcher"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
"yunion.io/x/onecloud/pkg/cloudnet/models"
)
func InitHandlers(app *appsrv.Application) {
db.InitAllManagers()
db.RegisterModelManager(db.OpsLog)
db.RegisterModelManager(db.TenantCacheManager)
db.RegisterModelManager(db.UserCacheManager)
db.RegisterModelManager(models.IfaceManager)
db.RegisterModelManager(models.IfacePeerManager)
db.RegisterModelManager(models.MeshNetworkMemberManager)
for _, manager := range []db.IModelManager{
models.RouterManager,
models.MeshNetworkManager,
models.RouteManager,
models.RuleManager,
} {
db.RegisterModelManager(manager)
handler := db.NewModelHandler(manager)
dispatcher.AddModelDispatcher("", app, handler)
}
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2019 Yunion
//
// 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.
package service
import (
"os"
_ "github.com/go-sql-driver/mysql"
"yunion.io/x/log"
"yunion.io/x/onecloud/pkg/cloudcommon"
common_app "yunion.io/x/onecloud/pkg/cloudcommon/app"
"yunion.io/x/onecloud/pkg/cloudcommon/db"
common_options "yunion.io/x/onecloud/pkg/cloudcommon/options"
"yunion.io/x/onecloud/pkg/cloudnet/models"
"yunion.io/x/onecloud/pkg/cloudnet/options"
)
func StartService() {
opts := &options.Options
common_options.ParseOptions(opts, os.Args, "cloudnet.conf", "cloudnet")
commonOpts := &opts.CommonOptions
common_app.InitAuth(commonOpts, func() {
log.Infof("Auth complete")
})
dbOpts := &opts.DBOptions
baseOpts := &opts.BaseOptions
app := common_app.InitApp(baseOpts, false)
InitHandlers(app)
db.EnsureAppInitSyncDB(app, dbOpts, models.InitDB)
defer cloudcommon.CloseDB()
common_app.ServeForever(app, baseOpts)
}
+1
View File
@@ -0,0 +1 @@
package utils // import "yunion.io/x/onecloud/pkg/cloudnet/utils"
+28
View File
@@ -0,0 +1,28 @@
package utils
import (
"fmt"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
func MustNewKey() wgtypes.Key {
key, err := wgtypes.GeneratePrivateKey()
if err != nil {
panic(fmt.Errorf("new key: %v", err))
}
return key
}
func MustNewKeyString() string {
key := MustNewKey()
return key.String()
}
func MustParseKeyString(k string) wgtypes.Key {
key, err := wgtypes.ParseKey(k)
if err != nil {
panic(fmt.Errorf("invalid key: %v", err))
}
return key
}
+36
View File
@@ -0,0 +1,36 @@
package utils
import (
"testing"
"golang.zx2c4.com/wireguard/wgctrl/wgtypes"
)
func TestKey(t *testing.T) {
t.Run("parse", func(t *testing.T) {
keyStr := "WJYVsrTtAae1QS9YzefV4OmVM6mkJglR+GEgxQpTs2g="
pubkeyStr := "mOX0S5AuRqd8lQZWcqTlzOS+veo404gE7NyV4u3xVkg="
pubkeyX, err := wgtypes.ParseKey(pubkeyStr)
if err != nil {
t.Fatalf("parse public key failed: %v", err)
}
key, err := wgtypes.ParseKey(keyStr)
if err != nil {
t.Fatalf("parse private key failed: %v", err)
}
pubkey := key.PublicKey()
if pubkey != pubkeyX {
t.Errorf("derived public key does not match expected.\ngot %#v\nwant %#v", pubkey, pubkeyX)
}
})
t.Run("generate", func(t *testing.T) {
key, err := wgtypes.GeneratePrivateKey()
if err != nil {
t.Fatalf("generate private key failed: %v", err)
}
pubkey := key.PublicKey()
t.Logf("private key: %s", key)
t.Logf(" public key: %s", pubkey)
})
}
+31
View File
@@ -0,0 +1,31 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
)
func NewCloudnetManager(keyword, keywordPlural string, columns, adminColumns []string) modulebase.ResourceManager {
return modulebase.ResourceManager{
BaseManager: *modulebase.NewBaseManager("cloudnet", "", "", columns, adminColumns),
Keyword: keyword, KeywordPlural: keywordPlural}
}
var (
registerV2 = func(mod modulebase.BaseManagerInterface) {
modulebase.Register("v2", mod)
}
)
+1
View File
@@ -0,0 +1 @@
package cloudnet // import "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet"
@@ -0,0 +1,42 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
)
type MeshNetworkManager struct {
modulebase.ResourceManager
}
var (
MeshNetworks MeshNetworkManager
)
func init() {
MeshNetworks = MeshNetworkManager{
NewCloudnetManager(
"meshnetwork",
"meshnetworks",
[]string{
"id",
"name",
},
[]string{"tenant"},
),
}
registerV2(&MeshNetworks)
}
@@ -0,0 +1,45 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
)
type RouterManager struct {
modulebase.ResourceManager
}
var (
Routers RouterManager
)
func init() {
Routers = RouterManager{
NewCloudnetManager(
"router",
"routers",
[]string{
"id",
"name",
"user",
"host",
"port",
},
[]string{"tenant"},
),
}
registerV2(&Routers)
}
@@ -0,0 +1,46 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
)
type RouteManager struct {
modulebase.ResourceManager
}
var (
Routes RouteManager
)
func init() {
Routes = RouteManager{
NewCloudnetManager(
"route",
"routes",
[]string{
"id",
"name",
"iface_id",
"ifname",
"network",
"gateway",
},
[]string{"tenant"},
),
}
registerV2(&Routes)
}
@@ -0,0 +1,51 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient/modulebase"
)
type RuleManager struct {
modulebase.ResourceManager
}
var (
Rules RuleManager
)
func init() {
Rules = RuleManager{
NewCloudnetManager(
"rule",
"rules",
[]string{
"id",
"name",
"router_id",
"match_src_net",
"match_dest_net",
"match_proto",
"match_port",
"match_in_ifname",
"match_out_ifname",
"action",
"action_options",
},
[]string{"tenant"},
),
}
registerV2(&Rules)
}
+1
View File
@@ -0,0 +1 @@
package cloudnet // import "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet"
@@ -0,0 +1,44 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient/options"
)
type MeshNetworkCreateOptions struct {
NAME string
}
type MeshNetworkGetOptions struct {
ID string `json:"-"`
}
type MeshNetworkUpdateOptions struct {
ID string `json:"-"`
Name string
}
type MeshNetworkDeleteOptions struct {
ID string `json:"-"`
}
type MeshNetworkListOptions struct {
options.BaseListOptions
}
type MeshNetworkActionRealizeOptions struct {
ID string `json:"-"`
}
+120
View File
@@ -0,0 +1,120 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"io/ioutil"
"yunion.io/x/jsonutils"
"yunion.io/x/onecloud/pkg/mcclient/options"
)
type RouterCreateOptions struct {
NAME string
User string
Host string
Port int
PrivateKey string
RealizeWgIfaces string `choices:"on|off" default:"on" help:"apply wg ifaces config on realization"`
RealizeRoutes string `choices:"on|off" default:"on" help:"apply routes config on realization"`
RealizeRules string `choices:"on|off" default:"on" help:"apply firewall rules on realization"`
}
func (opts *RouterCreateOptions) Params() (jsonutils.JSONObject, error) {
params, err := options.StructToParams(opts)
if err != nil {
return nil, err
}
if opts.PrivateKey != "" && opts.PrivateKey[0] == '@' {
data, err := ioutil.ReadFile(opts.PrivateKey[1:])
if err != nil {
return nil, err
}
params.Set("private_key", jsonutils.NewString(string(data)))
}
return params, nil
}
type RouterGetOptions struct {
ID string `json:"-"`
}
type RouterUpdateOptions struct {
ID string `json:"-"`
Name string
User string
Host string
Port int `json:",omitzero"`
PrivateKey string
RealizeWgIfaces string `json:",omitzero" choices:"on|off" help:"apply wg ifaces config on realization"`
RealizeRoutes string `json:",omitzero" choices:"on|off" help:"apply routes config on realization"`
RealizeRules string `json:",omitzero" choices:"on|off" help:"apply firewall rules on realization"`
}
func (opts *RouterUpdateOptions) Params() (jsonutils.JSONObject, error) {
params, err := options.StructToParams(opts)
if err != nil {
return nil, err
}
if opts.PrivateKey != "" && opts.PrivateKey[0] == '@' {
data, err := ioutil.ReadFile(opts.PrivateKey[1:])
if err != nil {
return nil, err
}
params.Set("private_key", jsonutils.NewString(string(data)))
}
return params, nil
}
type RouterDeleteOptions struct {
ID string `json:"-"`
}
type RouterListOptions struct {
options.BaseListOptions
}
type RouterActionJoinMeshNetworkOptions struct {
ID string `json:"-"`
MeshNetwork string
AdvertiseSubnets string `help:"cidr concatenated by comma"`
}
type RouterActionLeaveMeshNetworkOptions struct {
ID string `json:"-"`
MeshNetwork string
}
type RouterActionRegisterIfnameOptions struct {
ID string `json:"-"`
Ifname string
}
type RouterActionUnregisterIfnameOptions struct {
ID string `json:"-"`
Ifname string
}
type RouterActionRealizeOptions struct {
ID string `json:"-"`
}
+51
View File
@@ -0,0 +1,51 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient/options"
)
type RouteCreateOptions struct {
NAME string
Iface string `required:"true"`
Network string
Gateway string
}
type RouteGetOptions struct {
ID string `json:"-"`
}
type RouteUpdateOptions struct {
ID string `json:"-"`
Name string
Network string
Gateway string
}
type RouteDeleteOptions struct {
ID string `json:"-"`
}
type RouteListOptions struct {
options.BaseListOptions
Iface string
Network string
Gateway string
}
+77
View File
@@ -0,0 +1,77 @@
// Copyright 2019 Yunion
//
// 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.
package cloudnet
import (
"yunion.io/x/onecloud/pkg/mcclient/options"
)
type RuleCreateOptions struct {
NAME string
Router string `required:"true"`
MatchSrcNet string
MatchDestNet string
MatchProto string
MatchSrcPort int
MatchDestPort int
MatchInIfname string
MatchOutIfname string
Action string
ActionOptions string
}
type RuleGetOptions struct {
ID string `json:"-"`
}
type RuleUpdateOptions struct {
ID string `json:"-"`
Name string
MatchSrcNet string
MatchDestNet string
MatchProto string
MatchSrcPort int `json:",omitzero"`
MatchDestPort int `json:",omitzero"`
MatchInIfname string
MatchOutIfname string
Action string
ActionOptions string
}
type RuleDeleteOptions struct {
ID string `json:"-"`
}
type RuleListOptions struct {
options.BaseListOptions
Router string
MatchSrcNet string
MatchDestNet string
MatchProto string
MatchSrcPort int `json:",omitzero"`
MatchDestPort int `json:",omitzero"`
MatchInIfname string
MatchOutIfname string
Action string
ActionOptions string
}