diff --git a/build/cloudnet/root/etc/yunion/ansibleserver.conf.sample b/build/cloudnet/root/etc/yunion/ansibleserver.conf.sample new file mode 100644 index 0000000000..6f432cf75a --- /dev/null +++ b/build/cloudnet/root/etc/yunion/ansibleserver.conf.sample @@ -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 diff --git a/build/cloudnet/vars b/build/cloudnet/vars new file mode 100644 index 0000000000..bc3e44d9c0 --- /dev/null +++ b/build/cloudnet/vars @@ -0,0 +1 @@ +DESCRIPTION="Yunion Cloudnet" diff --git a/cmd/climc/climc.go b/cmd/climc/climc.go index d68b579b8f..31430dd147 100644 --- a/cmd/climc/climc.go +++ b/cmd/climc/climc.go @@ -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" diff --git a/cmd/climc/shell/cloudnet/common.go b/cmd/climc/shell/cloudnet/common.go new file mode 100644 index 0000000000..0ea0709712 --- /dev/null +++ b/cmd/climc/shell/cloudnet/common.go @@ -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 +) diff --git a/cmd/climc/shell/cloudnet/meshnetworks.go b/cmd/climc/shell/cloudnet/meshnetworks.go new file mode 100644 index 0000000000..357b5c26d7 --- /dev/null +++ b/cmd/climc/shell/cloudnet/meshnetworks.go @@ -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 + }) +} diff --git a/cmd/climc/shell/cloudnet/routers.go b/cmd/climc/shell/cloudnet/routers.go new file mode 100644 index 0000000000..d52583da83 --- /dev/null +++ b/cmd/climc/shell/cloudnet/routers.go @@ -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 + }) +} diff --git a/cmd/climc/shell/cloudnet/routes.go b/cmd/climc/shell/cloudnet/routes.go new file mode 100644 index 0000000000..7773ed881b --- /dev/null +++ b/cmd/climc/shell/cloudnet/routes.go @@ -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 + }) +} diff --git a/cmd/climc/shell/cloudnet/rules.go b/cmd/climc/shell/cloudnet/rules.go new file mode 100644 index 0000000000..ae4302a598 --- /dev/null +++ b/cmd/climc/shell/cloudnet/rules.go @@ -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 + }) +} diff --git a/cmd/cloudnet/main.go b/cmd/cloudnet/main.go new file mode 100644 index 0000000000..73690b6ac9 --- /dev/null +++ b/cmd/cloudnet/main.go @@ -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() +} diff --git a/pkg/cloudnet/models/doc.go b/pkg/cloudnet/models/doc.go new file mode 100644 index 0000000000..64cf742135 --- /dev/null +++ b/pkg/cloudnet/models/doc.go @@ -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" diff --git a/pkg/cloudnet/models/iface_peers.go b/pkg/cloudnet/models/iface_peers.go new file mode 100644 index 0000000000..f7afce1352 --- /dev/null +++ b/pkg/cloudnet/models/iface_peers.go @@ -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) +} diff --git a/pkg/cloudnet/models/ifaces.go b/pkg/cloudnet/models/ifaces.go new file mode 100644 index 0000000000..9286d3e0a2 --- /dev/null +++ b/pkg/cloudnet/models/ifaces.go @@ -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 +} diff --git a/pkg/cloudnet/models/initdb.go b/pkg/cloudnet/models/initdb.go new file mode 100644 index 0000000000..173a348fb5 --- /dev/null +++ b/pkg/cloudnet/models/initdb.go @@ -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 +} diff --git a/pkg/cloudnet/models/meshnetwork_members.go b/pkg/cloudnet/models/meshnetwork_members.go new file mode 100644 index 0000000000..7de54c6f8f --- /dev/null +++ b/pkg/cloudnet/models/meshnetwork_members.go @@ -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 +} diff --git a/pkg/cloudnet/models/meshnetworks.go b/pkg/cloudnet/models/meshnetworks.go new file mode 100644 index 0000000000..bba8923184 --- /dev/null +++ b/pkg/cloudnet/models/meshnetworks.go @@ -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 +} diff --git a/pkg/cloudnet/models/routers.go b/pkg/cloudnet/models/routers.go new file mode 100644 index 0000000000..2095c0171a --- /dev/null +++ b/pkg/cloudnet/models/routers.go @@ -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 +} diff --git a/pkg/cloudnet/models/routers_ansible.go b/pkg/cloudnet/models/routers_ansible.go new file mode 100644 index 0000000000..16049d2967 --- /dev/null +++ b/pkg/cloudnet/models/routers_ansible.go @@ -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 +} diff --git a/pkg/cloudnet/models/routers_ansible_wgX_conf_j2.go b/pkg/cloudnet/models/routers_ansible_wgX_conf_j2.go new file mode 100644 index 0000000000..eb967d91ef --- /dev/null +++ b/pkg/cloudnet/models/routers_ansible_wgX_conf_j2.go @@ -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 %} +` diff --git a/pkg/cloudnet/models/routers_deployment.go b/pkg/cloudnet/models/routers_deployment.go new file mode 100644 index 0000000000..be83194228 --- /dev/null +++ b/pkg/cloudnet/models/routers_deployment.go @@ -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 +} diff --git a/pkg/cloudnet/models/routers_realize.go b/pkg/cloudnet/models/routers_realize.go new file mode 100644 index 0000000000..ba3ed44f9a --- /dev/null +++ b/pkg/cloudnet/models/routers_realize.go @@ -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 +} diff --git a/pkg/cloudnet/models/routes.go b/pkg/cloudnet/models/routes.go new file mode 100644 index 0000000000..3080117127 --- /dev/null +++ b/pkg/cloudnet/models/routes.go @@ -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 +} diff --git a/pkg/cloudnet/models/rules.go b/pkg/cloudnet/models/rules.go new file mode 100644 index 0000000000..433f93a30f --- /dev/null +++ b/pkg/cloudnet/models/rules.go @@ -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) +} diff --git a/pkg/cloudnet/models/util_errors.go b/pkg/cloudnet/models/util_errors.go new file mode 100644 index 0000000000..add47bdfb1 --- /dev/null +++ b/pkg/cloudnet/models/util_errors.go @@ -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 +} diff --git a/pkg/cloudnet/models/util_subnets.go b/pkg/cloudnet/models/util_subnets.go new file mode 100644 index 0000000000..1b0d664f74 --- /dev/null +++ b/pkg/cloudnet/models/util_subnets.go @@ -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 +} diff --git a/pkg/cloudnet/options/doc.go b/pkg/cloudnet/options/doc.go new file mode 100644 index 0000000000..cc39592099 --- /dev/null +++ b/pkg/cloudnet/options/doc.go @@ -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" diff --git a/pkg/cloudnet/options/options.go b/pkg/cloudnet/options/options.go new file mode 100644 index 0000000000..a0e0459667 --- /dev/null +++ b/pkg/cloudnet/options/options.go @@ -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 +) diff --git a/pkg/cloudnet/service/doc.go b/pkg/cloudnet/service/doc.go new file mode 100644 index 0000000000..9480270440 --- /dev/null +++ b/pkg/cloudnet/service/doc.go @@ -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" diff --git a/pkg/cloudnet/service/handlers.go b/pkg/cloudnet/service/handlers.go new file mode 100644 index 0000000000..991c8061d2 --- /dev/null +++ b/pkg/cloudnet/service/handlers.go @@ -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) + } +} diff --git a/pkg/cloudnet/service/service.go b/pkg/cloudnet/service/service.go new file mode 100644 index 0000000000..63f9747fb0 --- /dev/null +++ b/pkg/cloudnet/service/service.go @@ -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) +} diff --git a/pkg/cloudnet/utils/doc.go b/pkg/cloudnet/utils/doc.go new file mode 100644 index 0000000000..bd6ede3e15 --- /dev/null +++ b/pkg/cloudnet/utils/doc.go @@ -0,0 +1 @@ +package utils // import "yunion.io/x/onecloud/pkg/cloudnet/utils" diff --git a/pkg/cloudnet/utils/key.go b/pkg/cloudnet/utils/key.go new file mode 100644 index 0000000000..1f2cee8fc8 --- /dev/null +++ b/pkg/cloudnet/utils/key.go @@ -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 +} diff --git a/pkg/cloudnet/utils/key_test.go b/pkg/cloudnet/utils/key_test.go new file mode 100644 index 0000000000..f2c2f39ace --- /dev/null +++ b/pkg/cloudnet/utils/key_test.go @@ -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) + }) +} diff --git a/pkg/mcclient/modules/cloudnet/common.go b/pkg/mcclient/modules/cloudnet/common.go new file mode 100644 index 0000000000..930c78ee3d --- /dev/null +++ b/pkg/mcclient/modules/cloudnet/common.go @@ -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) + } +) diff --git a/pkg/mcclient/modules/cloudnet/doc.go b/pkg/mcclient/modules/cloudnet/doc.go new file mode 100644 index 0000000000..219682d62a --- /dev/null +++ b/pkg/mcclient/modules/cloudnet/doc.go @@ -0,0 +1 @@ +package cloudnet // import "yunion.io/x/onecloud/pkg/mcclient/modules/cloudnet" diff --git a/pkg/mcclient/modules/cloudnet/mod_meshnetworks.go b/pkg/mcclient/modules/cloudnet/mod_meshnetworks.go new file mode 100644 index 0000000000..8bbd1339dc --- /dev/null +++ b/pkg/mcclient/modules/cloudnet/mod_meshnetworks.go @@ -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) +} diff --git a/pkg/mcclient/modules/cloudnet/mod_routers.go b/pkg/mcclient/modules/cloudnet/mod_routers.go new file mode 100644 index 0000000000..ee5c0051b3 --- /dev/null +++ b/pkg/mcclient/modules/cloudnet/mod_routers.go @@ -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) +} diff --git a/pkg/mcclient/modules/cloudnet/mod_routes.go b/pkg/mcclient/modules/cloudnet/mod_routes.go new file mode 100644 index 0000000000..8a65bb288c --- /dev/null +++ b/pkg/mcclient/modules/cloudnet/mod_routes.go @@ -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) +} diff --git a/pkg/mcclient/modules/cloudnet/mod_rules.go b/pkg/mcclient/modules/cloudnet/mod_rules.go new file mode 100644 index 0000000000..bbc8ac390a --- /dev/null +++ b/pkg/mcclient/modules/cloudnet/mod_rules.go @@ -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) +} diff --git a/pkg/mcclient/options/cloudnet/doc.go b/pkg/mcclient/options/cloudnet/doc.go new file mode 100644 index 0000000000..923a95ad11 --- /dev/null +++ b/pkg/mcclient/options/cloudnet/doc.go @@ -0,0 +1 @@ +package cloudnet // import "yunion.io/x/onecloud/pkg/mcclient/options/cloudnet" diff --git a/pkg/mcclient/options/cloudnet/meshnetworks.go b/pkg/mcclient/options/cloudnet/meshnetworks.go new file mode 100644 index 0000000000..4ec19fe265 --- /dev/null +++ b/pkg/mcclient/options/cloudnet/meshnetworks.go @@ -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:"-"` +} diff --git a/pkg/mcclient/options/cloudnet/routers.go b/pkg/mcclient/options/cloudnet/routers.go new file mode 100644 index 0000000000..781297fd4c --- /dev/null +++ b/pkg/mcclient/options/cloudnet/routers.go @@ -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:"-"` +} diff --git a/pkg/mcclient/options/cloudnet/routes.go b/pkg/mcclient/options/cloudnet/routes.go new file mode 100644 index 0000000000..d9062ad7ab --- /dev/null +++ b/pkg/mcclient/options/cloudnet/routes.go @@ -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 +} diff --git a/pkg/mcclient/options/cloudnet/rules.go b/pkg/mcclient/options/cloudnet/rules.go new file mode 100644 index 0000000000..9607e38f9f --- /dev/null +++ b/pkg/mcclient/options/cloudnet/rules.go @@ -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 +}