diff --git a/cmd/climc/shell/compute/net_tap_flows.go b/cmd/climc/shell/compute/net_tap_flows.go new file mode 100644 index 0000000000..102169b799 --- /dev/null +++ b/cmd/climc/shell/compute/net_tap_flows.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 compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" + options "yunion.io/x/onecloud/pkg/mcclient/options/compute" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.NetTapFlows) + cmd.Create(&options.NetTapFlowCreateOptions{}) + cmd.List(&options.NetTapFlowListOptions{}) + cmd.Show(&options.NetTapFlowIdOptions{}) + cmd.Delete(&options.NetTapFlowIdOptions{}) + cmd.Perform("enable", &options.NetTapFlowIdOptions{}) + cmd.Perform("disable", &options.NetTapFlowIdOptions{}) +} diff --git a/cmd/climc/shell/compute/net_tap_services.go b/cmd/climc/shell/compute/net_tap_services.go new file mode 100644 index 0000000000..492825ce31 --- /dev/null +++ b/cmd/climc/shell/compute/net_tap_services.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 compute + +import ( + "yunion.io/x/onecloud/cmd/climc/shell" + modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" + options "yunion.io/x/onecloud/pkg/mcclient/options/compute" +) + +func init() { + cmd := shell.NewResourceCmd(&modules.NetTapServices) + cmd.Create(&options.NetTapServiceCreateOptions{}) + cmd.List(&options.NetTapServiceListOptions{}) + cmd.Show(&options.NetTapServiceIdOptions{}) + cmd.Delete(&options.NetTapServiceIdOptions{}) + cmd.Perform("enable", &options.NetTapServiceIdOptions{}) + cmd.Perform("disable", &options.NetTapServiceIdOptions{}) +} diff --git a/pkg/apis/compute/net_tap_flows.go b/pkg/apis/compute/net_tap_flows.go new file mode 100644 index 0000000000..cae90845ac --- /dev/null +++ b/pkg/apis/compute/net_tap_flows.go @@ -0,0 +1,84 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + TapFlowVSwitch = "vswitch" + TapFlowGuestNic = "vnic" + + TapFlowDirectionIn = "IN" + TapFlowDirectionOut = "OUT" + TapFlowDirectionBoth = "BOTH" + + TapFlowIdMin = 0x10 + TapFlowIdMax = 0x7fff +) + +var ( + TapFlowDirections = []string{ + TapFlowDirectionIn, + TapFlowDirectionOut, + TapFlowDirectionBoth, + } +) + +type NetTapFlowListInput struct { + apis.EnabledStatusStandaloneResourceListInput + + TapId string `json:"tap_id"` +} + +type NetTapFlowDetails struct { + apis.EnabledStatusStandaloneResourceDetails + + // 关联的tap服务名称 + Tap string `json:"tap"` + + Source string `json:"source"` + + SourceIps string `json:"source_ips"` + + Net string `json:"net"` +} + +type NetTapFlowCreateInput struct { + apis.EnabledStatusStandaloneResourceCreateInput + + TapId string `json:"tap_id" required:"true" help:"tap service id or name that this flow belongs to"` + + Type string `json:"type" required:"true" choices:"vswitch|vnic" help:"type of tap flow"` + + HostId string `json:"host_id" help:"id or name of host to tap with"` + + WireId string `json:"wire_id" help:"id or name of wire to tap with"` + + VlanId int `json:"vlan_id" help:"vlan id of vswitch to tap with"` + + GuestId string `json:"guest_id" help:"id or name of vm to tap with"` + + // swagger:ignore + NetId string `json:"net_id" ignore:"true"` + + MacAddr string `json:"mac_addr" help:"mac address of guest nic to tap with"` + + IpAddr string `json:"ip_addr" help:"ip address of guest nic to tap with"` + + // swagger:ignore + SourceId string `json:"source_id" ignore:"true"` + + Direction string `json:"direction" help:"flow direction" choices:"IN|OUT|BOTH"` +} diff --git a/pkg/apis/compute/net_tap_services.go b/pkg/apis/compute/net_tap_services.go new file mode 100644 index 0000000000..818a42ebd4 --- /dev/null +++ b/pkg/apis/compute/net_tap_services.go @@ -0,0 +1,52 @@ +// 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 compute + +import "yunion.io/x/onecloud/pkg/apis" + +const ( + TapServiceHost = "host" + TapServiceGuest = "guest" +) + +type NetTapServiceListInput struct { + apis.EnabledStatusStandaloneResourceListInput +} + +type NetTapServiceDetails struct { + apis.EnabledStatusStandaloneResourceDetails + + // 流量镜像目标名称 + Target string `json:"target"` + + // 流量镜像目标IP地址 + TargetIps string `json:"target_ips"` + + // tap flow数量 + FlowCount int `json:"flow_count"` +} + +type NetTapServiceCreateInput struct { + apis.EnabledStatusStandaloneResourceCreateInput + + // TAP服务类型,监听宿主机的网卡还是虚拟机的网卡, 可能值为 host|guest + Type string `json:"type" required:"true" choices:"host|guest" help:"type of tap service"` + + // 资源ID,如果Type=host,该值为宿主机的ID,如果Type=guest,该值为虚拟机的ID + TargetId string `json:"target_id" required:"true" help:"id of target device"` + + // 监听网卡的Mac地址 + MacAddr string `json:"mac_addr" help:"mac address of the device interface for tappping"` +} diff --git a/pkg/compute/models/guestnetworks.go b/pkg/compute/models/guestnetworks.go index bf987b4327..22767e620e 100644 --- a/pkg/compute/models/guestnetworks.go +++ b/pkg/compute/models/guestnetworks.go @@ -18,7 +18,6 @@ import ( "context" "database/sql" "fmt" - "math/rand" "regexp" "time" @@ -69,6 +68,7 @@ func init() { } GuestnetworkManager.SetVirtualObject(GuestnetworkManager) GuestnetworkManager.TableSpec().AddIndex(true, "ip_addr", "guest_id") + GuestnetworkManager.TableSpec().AddIndex(false, "mac_addr", "deleted") }) } @@ -78,7 +78,7 @@ type SGuestnetwork struct { NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" ` // MAC地址 - MacAddr string `width:"32" charset:"ascii" nullable:"false" list:"user"` + MacAddr string `width:"32" charset:"ascii" nullable:"false" list:"user" index:"true"` // IPv4地址 IpAddr string `width:"16" charset:"ascii" nullable:"false" list:"user"` // IPv6地址 @@ -209,37 +209,12 @@ func (manager *SGuestnetworkManager) fetchByRowId( return &gns[0], nil } -const MAX_TRIES = 10 +func (manager *SGuestnetworkManager) GenerateMac(suggestion string) (string, error) { + return generateMac(suggestion) +} -func (manager *SGuestnetworkManager) GenerateMac(netId string, suggestion string) (string, error) { - for tried := 0; tried < MAX_TRIES; tried += 1 { - var mac string - if len(suggestion) > 0 && regutils.MatchMacAddr(suggestion) { - mac = suggestion - suggestion = "" - } else { - b := make([]byte, 4) - _, err := rand.Read(b) - if err != nil { - log.Errorf("generate random mac failed: %s", err) - continue - } - mac = fmt.Sprintf("%s:%02x:%02x:%02x:%02x", options.Options.GlobalMacPrefix, b[0], b[1], b[2], b[3]) - } - q := manager.Query().Equals("mac_addr", mac) - if len(netId) > 0 { - q = q.Equals("network_id", netId) - } - cnt, err := q.CountWithError() - if err != nil { - log.Errorf("find mac %s error %s", mac, err) - return "", err - } - if cnt == 0 { - return mac, nil - } - } - return "", fmt.Errorf("maximal retry reached") +func (manager *SGuestnetworkManager) FilterByMac(mac string) *sqlchemy.SQuery { + return manager.Query().Equals("mac_addr", mac) } type newGuestNetworkArgs struct { @@ -313,7 +288,7 @@ func (manager *SGuestnetworkManager) newGuestNetwork( provider := vpc.GetProviderName() - macAddr, err := manager.GenerateMac(network.Id, mac) + macAddr, err := manager.GenerateMac(mac) if err != nil { return nil, err } diff --git a/pkg/compute/models/loadbalancernetworks.go b/pkg/compute/models/loadbalancernetworks.go index 76a9b04844..2ff0b3d576 100644 --- a/pkg/compute/models/loadbalancernetworks.go +++ b/pkg/compute/models/loadbalancernetworks.go @@ -115,7 +115,7 @@ func (m *SLoadbalancernetworkManager) NewLoadbalancerNetwork(ctx context.Context lockman.LockObject(ctx, network) defer lockman.ReleaseObject(ctx, network) if req.Loadbalancer.NetworkType == api.LB_NETWORK_TYPE_VPC { - macAddr, err := GuestnetworkManager.GenerateMac(network.Id, "") + macAddr, err := GuestnetworkManager.GenerateMac("") if err != nil { return nil, errors.Wrapf(err, "generate macaddr") } diff --git a/pkg/compute/models/mac_generator.go b/pkg/compute/models/mac_generator.go new file mode 100644 index 0000000000..55d740b2a0 --- /dev/null +++ b/pkg/compute/models/mac_generator.go @@ -0,0 +1,72 @@ +// 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 ( + "fmt" + "math/rand" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/util/regutils" + "yunion.io/x/sqlchemy" + + "yunion.io/x/onecloud/pkg/compute/options" + "yunion.io/x/onecloud/pkg/httperrors" +) + +type IMacGenerator interface { + FilterByMac(mac string) *sqlchemy.SQuery +} + +const maxMacTries = 10 + +func generateMac(suggestion string) (string, error) { + for tried := 0; tried < maxMacTries; tried += 1 { + var mac string + if len(suggestion) > 0 && regutils.MatchMacAddr(suggestion) { + mac = suggestion + suggestion = "" + } else { + b := make([]byte, 4) + _, err := rand.Read(b) + if err != nil { + log.Errorf("generate random mac failed: %s", err) + continue + } + mac = fmt.Sprintf("%s:%02x:%02x:%02x:%02x", options.Options.GlobalMacPrefix, b[0], b[1], b[2], b[3]) + } + found := false + for _, man := range []IMacGenerator{ + GuestnetworkManager, + NetTapServiceManager, + } { + q := man.FilterByMac(mac) + cnt, err := q.CountWithError() + if err != nil { + log.Errorf("find mac %s error %s", mac, err) + return "", err + } + if cnt > 0 { + found = true + break + } + } + if !found { + return mac, nil + } + } + return "", errors.Wrap(httperrors.ErrTooManyAttempts, "maximal retry reached") +} diff --git a/pkg/compute/models/natgateways.go b/pkg/compute/models/natgateways.go index d8f1df2132..c6e95ca269 100644 --- a/pkg/compute/models/natgateways.go +++ b/pkg/compute/models/natgateways.go @@ -144,7 +144,13 @@ func (man *SNatGatewayManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field return q, httperrors.ErrNotFound } -func (man *SNatGatewayManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.NatgatewayCreateInput) (api.NatgatewayCreateInput, error) { +func (man *SNatGatewayManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input api.NatgatewayCreateInput, +) (api.NatgatewayCreateInput, error) { if len(input.NetworkId) == 0 { return input, httperrors.NewMissingParameterError("network_id") } @@ -216,7 +222,13 @@ func (man *SNatGatewayManager) ValidateCreateData(ctx context.Context, userCred return driver.ValidateCreateNatGateway(ctx, userCred, input) } -func (self *SNatGateway) PostCreate(ctx context.Context, userCred mcclient.TokenCredential, ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, data jsonutils.JSONObject) { +func (self *SNatGateway) PostCreate( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + data jsonutils.JSONObject, +) { self.SInfrasResourceBase.PostCreate(ctx, userCred, ownerId, query, data) err := self.StartNatGatewayCreateTask(ctx, userCred, data.(*jsonutils.JSONDict)) @@ -348,7 +360,7 @@ func (self *SNatGateway) GetDTableSize(filter func(q *sqlchemy.SQuery) *sqlchemy return q.CountWithError() } -func (manager SNatGatewayManager) FetchCustomizeColumns( +func (manager *SNatGatewayManager) FetchCustomizeColumns( ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, diff --git a/pkg/compute/models/net_tap_flows.go b/pkg/compute/models/net_tap_flows.go new file mode 100644 index 0000000000..9796c33290 --- /dev/null +++ b/pkg/compute/models/net_tap_flows.go @@ -0,0 +1,337 @@ +// 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" + "database/sql" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/utils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SNetTapFlowManager struct { + db.SEnabledStatusStandaloneResourceBaseManager +} + +var NetTapFlowManager *SNetTapFlowManager + +func init() { + NetTapFlowManager = &SNetTapFlowManager{ + SEnabledStatusStandaloneResourceBaseManager: db.NewEnabledStatusStandaloneResourceBaseManager( + SNetTapFlow{}, + "net_tap_flows_tbl", + "tap_flow", + "tap_flows", + ), + } + NetTapFlowManager.SetVirtualObject(NetTapFlowManager) +} + +type SNetTapFlow struct { + db.SEnabledStatusStandaloneResourceBase + + TapId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` + + Type string `width:"10" charset:"ascii" list:"admin" create:"admin_required"` + + SourceId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` + + NetId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` + + MacAddr string `width:"18" charset:"ascii" list:"admin" create:"admin_optional"` + + VlanId int `nullable:"true" list:"admin" create:"admin_optional"` + + Direction string `width:"6" charset:"ascii" list:"admin" create:"admin_required" default:"BOTH"` + + FlowId uint16 `nullable:"false" list:"admin"` +} + +func (man *SNetTapFlowManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.NetTapFlowListInput, +) (*sqlchemy.SQuery, error) { + q, err := man.SEnabledStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.EnabledStatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SEnabledStatusInfrasResourceBaseManager.ListItemFilter") + } + if len(query.TapId) > 0 { + tapObj, err := NetTapServiceManager.FetchByIdOrName(userCred, query.TapId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s not found", NetTapServiceManager.Keyword(), query.TapId) + } else { + return nil, errors.Wrap(err, "NetTapServiceManager.FetchByIdOrName") + } + } + q = q.Equals("tap_id", tapObj.GetId()) + } + return q, nil +} + +func (man *SNetTapFlowManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.NetTapFlowListInput, +) (*sqlchemy.SQuery, error) { + q, err := man.SEnabledStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.EnabledStatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SEnabledStatusInfrasResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +func (man *SNetTapFlowManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + q, err = man.SEnabledStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + return q, httperrors.ErrNotFound +} + +func (manager *SNetTapFlowManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.NetTapFlowDetails { + rows := make([]api.NetTapFlowDetails, len(objs)) + stdRows := manager.SEnabledStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + tapIds := make([]string, len(objs)) + for i := range rows { + rows[i] = api.NetTapFlowDetails{ + EnabledStatusStandaloneResourceDetails: stdRows[i], + } + flow := objs[i].(*SNetTapFlow) + tapIds[i] = flow.TapId + } + tapIdMap, err := db.FetchIdNameMap2(NetTapServiceManager, tapIds) + if err != nil { + log.Errorf("FetchIdNameMap2 fail: %s", err) + return rows + } + for i := range rows { + if name, ok := tapIdMap[tapIds[i]]; ok { + rows[i].Tap = name + } + } + return rows +} + +func (flow *SNetTapFlow) getMoreDetails(ctx context.Context, details api.NetTapFlowDetails) api.NetTapFlowDetails { + switch flow.Type { + case api.TapFlowVSwitch: + host := HostManager.FetchHostById(flow.SourceId) + details.Source = host.Name + details.SourceIps = host.AccessIp + wire := WireManager.FetchWireById(flow.NetId) + details.Net = wire.Name + case api.TapFlowGuestNic: + guest := GuestManager.FetchGuestById(flow.SourceId) + details.Source = guest.Name + ret := fetchGuestIPs([]string{flow.SourceId}, tristate.False) + details.SourceIps = strings.Join(ret[flow.SourceId], ",") + netObj, _ := NetworkManager.FetchById(flow.NetId) + details.Net = netObj.GetName() + } + return details +} + +func (manager *SNetTapFlowManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input api.NetTapFlowCreateInput, +) (api.NetTapFlowCreateInput, error) { + var err error + input.EnabledStatusStandaloneResourceCreateInput, err = manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledStatusStandaloneResourceCreateInput) + if err != nil { + return input, errors.Wrap(err, "SEnabledStatusInfrasResourceBaseManager.ValidateCreateData(") + } + tapObj, err := NetTapServiceManager.FetchByIdOrName(userCred, input.TapId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return input, httperrors.NewResourceNotFoundError2(NetTapServiceManager.Keyword(), input.TapId) + } else { + return input, errors.Wrap(err, "NetTapServiceManager.FetchByIdOrName") + } + } + input.TapId = tapObj.GetId() + switch input.Type { + case api.TapFlowVSwitch: + hostObj, err := HostManager.FetchByIdOrName(userCred, input.HostId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return input, httperrors.NewResourceNotFoundError2(HostManager.Keyword(), input.HostId) + } else { + return input, errors.Wrap(err, "HostManager.FetchByIdOrName") + } + } + wireObj, err := WireManager.FetchByIdOrName(userCred, input.WireId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return input, httperrors.NewResourceNotFoundError2(WireManager.Keyword(), input.WireId) + } else { + return input, errors.Wrap(err, "WireManager.FetchByIdOrName") + } + } + host := hostObj.(*SHost) + if host.HostType != api.HOST_TYPE_HYPERVISOR { + return input, errors.Wrapf(httperrors.ErrNotSupported, "host type %s not supported", host.HostType) + } + wire := wireObj.(*SWire) + netifs := host.GetNetifsOnWire(wire) + if len(netifs) == 0 { + return input, errors.Wrapf(httperrors.ErrInvalidStatus, "host %s and wire %s not attached", input.HostId, input.WireId) + } + ipmiCnt := 0 + nicCnt := 0 + for _, netif := range netifs { + if netif.NicType == api.NIC_TYPE_IPMI { + ipmiCnt++ + } + nicCnt++ + } + if ipmiCnt == nicCnt { + return input, errors.Wrapf(httperrors.ErrInvalidStatus, "host %s and wire %s attached with IPMI links", input.HostId, input.WireId) + } + input.SourceId = host.Id + input.MacAddr = "" + input.NetId = wire.Id + if input.VlanId <= 0 || input.VlanId > 4095 { + return input, errors.Wrapf(httperrors.ErrInputParameter, "invalid vlan id %d", input.VlanId) + } + case api.TapFlowGuestNic: + guestObj, err := GuestManager.FetchByIdOrName(userCred, input.GuestId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return input, httperrors.NewResourceNotFoundError2(GuestManager.Keyword(), input.GuestId) + } else { + return input, errors.Wrap(err, "GuestManager.FetchByIdOrName") + } + } + guest := guestObj.(*SGuest) + if guest.Hypervisor != api.HYPERVISOR_KVM { + return input, errors.Wrapf(httperrors.ErrInvalidStatus, "hypervisor %s not supported", guest.Hypervisor) + } + gns, err := GuestnetworkManager.FetchByGuestId(guest.Id) + if err != nil { + return input, errors.Wrap(err, "GuestnetworkManager.FetchByGuestId") + } + var gn *SGuestnetwork + if len(input.IpAddr) == 0 && len(input.MacAddr) == 0 { + if len(gns) == 1 { + gn = &gns[0] + } else { + return input, errors.Wrap(httperrors.ErrInputParameter, "either ip_addr or mac_addr should be specified") + } + } else { + for i := range gns { + if (len(input.IpAddr) > 0 && input.IpAddr == gns[i].IpAddr) || (len(input.MacAddr) > 0 && input.MacAddr == gns[i].MacAddr) { + gn = &gns[i] + break + } + } + if gn == nil { + return input, errors.Wrap(httperrors.ErrNotFound, "Guest network not found") + } + } + input.SourceId = guest.Id + input.MacAddr = gn.MacAddr + input.NetId = gn.NetworkId + input.VlanId = 0 + default: + return input, errors.Wrapf(httperrors.ErrInputParameter, "invalid flow type %s", input.Type) + } + if len(input.Direction) == 0 { + input.Direction = api.TapFlowDirectionBoth + } + if !utils.IsInStringArray(input.Direction, api.TapFlowDirections) { + return input, errors.Wrapf(httperrors.ErrNotSupported, "unsupported direction %s", input.Direction) + } + return input, nil +} + +func (tap *SNetTapFlow) CustomizeCreate( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + data jsonutils.JSONObject, +) error { + // generate flowId + err := func() error { + lockman.LockClass(ctx, NetTapFlowManager, "") + defer lockman.ReleaseClass(ctx, NetTapFlowManager, "") + + flowId, err := NetTapFlowManager.getFreeFlowId() + if err != nil { + return errors.Wrap(err, "getFreeFlowId") + } + tap.FlowId = flowId + return nil + }() + if err != nil { + return errors.Wrap(err, "generate flow id") + } + return tap.SEnabledStatusStandaloneResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) +} + +func (manager *SNetTapFlowManager) getFreeFlowId() (uint16, error) { + flowIds := make([]struct { + FlowId uint16 `json:"flow_id"` + }, 0) + q := manager.Query("flow_id").Asc("flow_id") + err := q.All(&flowIds) + if err != nil && errors.Cause(err) != sql.ErrNoRows { + return 0, errors.Wrap(err, "queryAll") + } + if len(flowIds) == 0 { + return api.TapFlowIdMin, nil + } + if flowIds[0].FlowId > api.TapFlowIdMin { + return flowIds[0].FlowId - 1, nil + } + if flowIds[len(flowIds)-1].FlowId < api.TapFlowIdMax { + return flowIds[len(flowIds)-1].FlowId + 1, nil + } + for i := 0; i < len(flowIds)-1; i++ { + if flowIds[i].FlowId+1 < flowIds[i+1].FlowId { + return flowIds[i].FlowId + 1, nil + } + } + return 0, errors.Wrap(httperrors.ErrOutOfResource, "run out of flow id!!!") +} diff --git a/pkg/compute/models/net_tap_services.go b/pkg/compute/models/net_tap_services.go new file mode 100644 index 0000000000..7e2d096f28 --- /dev/null +++ b/pkg/compute/models/net_tap_services.go @@ -0,0 +1,325 @@ +// 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" + "database/sql" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "yunion.io/x/pkg/tristate" + "yunion.io/x/pkg/util/netutils" + "yunion.io/x/sqlchemy" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/cloudcommon/db" + "yunion.io/x/onecloud/pkg/cloudcommon/db/lockman" + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/seclib2" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SNetTapServiceManager struct { + db.SEnabledStatusStandaloneResourceBaseManager +} + +var NetTapServiceManager *SNetTapServiceManager + +func init() { + NetTapServiceManager = &SNetTapServiceManager{ + SEnabledStatusStandaloneResourceBaseManager: db.NewEnabledStatusStandaloneResourceBaseManager( + SNetTapService{}, + "net_tap_services_tbl", + "tap_service", + "tap_services", + ), + } + NetTapServiceManager.SetVirtualObject(NetTapServiceManager) + NetTapServiceManager.TableSpec().AddIndex(false, "mac_addr", "deleted") +} + +type SNetTapService struct { + db.SEnabledStatusStandaloneResourceBase + + // 流量采集端类型,虚拟机(guest)还是宿主机(host) + Type string `width:"10" charset:"ascii" list:"admin" create:"admin_required"` + // 接受流量的目标ID,如果type=host,是hostId,如果type=guest,是guestId + TargetId string `width:"36" charset:"ascii" nullable:"false" list:"admin" create:"admin_required"` + // 接受流量的Mac地址 + MacAddr string `width:"18" charset:"ascii" list:"admin" create:"admin_optional"` + // 网卡名称 + Ifname string `width:"16" charset:"ascii" nullable:"true" list:"admin"` +} + +func (man *SNetTapServiceManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.NetTapServiceListInput, +) (*sqlchemy.SQuery, error) { + q, err := man.SEnabledStatusStandaloneResourceBaseManager.ListItemFilter(ctx, q, userCred, query.EnabledStatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SEnabledStatusStandaloneResourceBaseManager.ListItemFilter") + } + return q, nil +} + +func (man *SNetTapServiceManager) OrderByExtraFields( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + query api.NetTapServiceListInput, +) (*sqlchemy.SQuery, error) { + q, err := man.SEnabledStatusStandaloneResourceBaseManager.OrderByExtraFields(ctx, q, userCred, query.EnabledStatusStandaloneResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SEnabledStatusInfrasResourceBaseManager.OrderByExtraFields") + } + return q, nil +} + +func (man *SNetTapServiceManager) QueryDistinctExtraField(q *sqlchemy.SQuery, field string) (*sqlchemy.SQuery, error) { + var err error + q, err = man.SEnabledStatusStandaloneResourceBaseManager.QueryDistinctExtraField(q, field) + if err == nil { + return q, nil + } + + return q, httperrors.ErrNotFound +} + +func (manager *SNetTapServiceManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.NetTapServiceDetails { + rows := make([]api.NetTapServiceDetails, len(objs)) + stdRows := manager.SEnabledStatusStandaloneResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + for i := range rows { + rows[i] = api.NetTapServiceDetails{ + EnabledStatusStandaloneResourceDetails: stdRows[i], + } + rows[i] = objs[i].(*SNetTapService).getMoreDetails(ctx, rows[i]) + } + return rows +} + +func (srv *SNetTapService) getMoreDetails(ctx context.Context, details api.NetTapServiceDetails) api.NetTapServiceDetails { + var err error + switch srv.Type { + case api.TapServiceHost: + host := HostManager.FetchHostById(srv.TargetId) + details.Target = host.Name + details.TargetIps = host.AccessIp + case api.TapServiceGuest: + guest := GuestManager.FetchGuestById(srv.TargetId) + details.Target = guest.Name + ret := fetchGuestIPs([]string{srv.TargetId}, tristate.False) + details.TargetIps = strings.Join(ret[srv.TargetId], ",") + } + details.FlowCount, err = srv.getFlowsCount() + if err != nil { + log.Errorf("getFlowsCount %s", err) + } + return details +} + +func (srv *SNetTapService) getFlowsQuery() *sqlchemy.SQuery { + return NetTapFlowManager.Query().Equals("tap_id", srv.Id) +} + +func (srv *SNetTapService) getFlowsCount() (int, error) { + return srv.getFlowsQuery().CountWithError() +} + +func (srv *SNetTapService) getFlows() ([]SNetTapFlow, error) { + flows := make([]SNetTapFlow, 0) + q := srv.getFlowsQuery() + err := db.FetchModelObjects(NetTapFlowManager, q, &flows) + if err != nil { + return nil, errors.Wrap(err, "FetchModelObjects") + } + return flows, nil +} + +func (srv *SNetTapService) ValidateDeleteCondition(ctx context.Context, info jsonutils.JSONObject) error { + cnt, err := srv.getFlowsCount() + if err != nil { + return errors.Wrap(err, "getFlowCount") + } + if cnt > 0 { + return httperrors.NewNotEmptyError("Tap service has associated flows") + } + return srv.SEnabledStatusStandaloneResourceBase.ValidateDeleteCondition(ctx, info) +} + +func (manager *SNetTapServiceManager) ValidateCreateData( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + input api.NetTapServiceCreateInput, +) (api.NetTapServiceCreateInput, error) { + var err error + input.EnabledStatusStandaloneResourceCreateInput, err = manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, input.EnabledStatusStandaloneResourceCreateInput) + if err != nil { + return input, errors.Wrap(err, "SEnabledStatusInfrasResourceBaseManager.ValidateCreateData(") + } + switch input.Type { + case api.TapServiceHost: + hostObj, err := HostManager.FetchByIdOrName(userCred, input.TargetId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return input, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", HostManager.Keyword(), input.TargetId) + } else { + return input, errors.Wrap(err, "HostManager.FetchByIdOrName") + } + } + host := hostObj.(*SHost) + if host.HostType != api.HOST_TYPE_HYPERVISOR { + return input, errors.Wrapf(httperrors.ErrNotSupported, "host type %s not supported", host.HostType) + } + if len(input.MacAddr) > 0 { + input.MacAddr = netutils.FormatMacAddr(input.MacAddr) + nic := host.GetNetInterface(input.MacAddr) + if nic == nil { + return input, errors.Wrap(errors.ErrNotFound, "host.GetNetInterface") + } + if len(nic.WireId) > 0 { + return input, errors.Wrap(httperrors.ErrNotEmpty, "interface has been used") + } + } + _, err = manager.fetchByHostIdMac(hostObj.GetId(), input.MacAddr) + if err == nil { + return input, errors.Wrapf(httperrors.ErrNotEmpty, "host %s(%s) has been attached to tap service", input.TargetId, input.MacAddr) + } else if errors.Cause(err) != sql.ErrNoRows { + return input, errors.Wrap(err, "fetchGuestById") + } + input.TargetId = hostObj.GetId() + case api.TapServiceGuest: + guestObj, err := GuestManager.FetchByIdOrName(userCred, input.TargetId) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return input, errors.Wrapf(httperrors.ErrResourceNotFound, "%s %s", GuestManager.Keyword(), input.TargetId) + } else { + return input, errors.Wrap(err, "GuestManager.FetchByIdOrName") + } + } + guest := guestObj.(*SGuest) + if guest.Hypervisor != api.HYPERVISOR_KVM { + return input, errors.Wrapf(httperrors.ErrNotSupported, "hypervisor %s not supported", guest.Hypervisor) + } + // check the guest attach to tap + _, err = manager.fetchByGuestId(guestObj.GetId()) + if err == nil { + return input, errors.Wrapf(httperrors.ErrNotEmpty, "guest %s has been attached to tap service", input.TargetId) + } else if errors.Cause(err) != sql.ErrNoRows { + return input, errors.Wrap(err, "fetchGuestById") + } + input.TargetId = guestObj.GetId() + default: + return input, errors.Wrapf(httperrors.ErrNotSupported, "unsupported type %s", input.Type) + } + return input, nil +} + +func (man *SNetTapServiceManager) fetchByGuestId(guestId string) (*SNetTapService, error) { + return man.fetchByTargetMac(api.TapServiceGuest, guestId, "") +} + +func (man *SNetTapServiceManager) fetchByHostIdMac(hostId, mac string) (*SNetTapService, error) { + return man.fetchByTargetMac(api.TapServiceHost, hostId, mac) +} + +func (man *SNetTapServiceManager) fetchByTargetMac(tapTyep, targetId, mac string) (*SNetTapService, error) { + q := man.Query().Equals("type", api.TapServiceGuest).Equals("target_id", targetId) + if len(mac) > 0 { + q = q.Equals("mac_addr", mac) + } + tapObj, err := db.NewModelObject(man) + if err != nil { + return nil, errors.Wrap(err, "NewModelObject") + } + err = q.First(tapObj) + if err != nil { + return nil, errors.Wrap(err, "First") + } + return tapObj.(*SNetTapService), nil +} + +func (tap *SNetTapService) CustomizeCreate( + ctx context.Context, + userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, + query jsonutils.JSONObject, + data jsonutils.JSONObject, +) error { + input := api.NetTapServiceCreateInput{} + err := data.Unmarshal(&input) + if err != nil { + return errors.Wrap(err, "Unmarshal NetTapServiceCreateInput") + } + switch input.Type { + case api.TapServiceGuest: + err := func() error { + lockman.LockClass(ctx, NetTapServiceManager, "") + defer lockman.ReleaseClass(ctx, NetTapServiceManager, "") + // generate mac + mac, err := NetTapServiceManager.GenerateMac(input.MacAddr) + if err != nil { + return errors.Wrap(err, "GenerateMac") + } + ifname, err := NetTapServiceManager.generateIfname(mac) + if err != nil { + return errors.Wrap(err, "generateIfname") + } + tap.Ifname = ifname + tap.MacAddr = mac + return nil + }() + if err != nil { + return errors.Wrap(err, "generate mac and ifname") + } + } + return tap.SEnabledStatusStandaloneResourceBase.CustomizeCreate(ctx, userCred, ownerId, query, data) +} + +func (manager *SNetTapServiceManager) generateIfname(seed string) (string, error) { + for tried := 0; tried < maxMacTries; tried++ { + ifname := "tap" + seclib2.HashId(seed, byte(tried), 6) + cnt, err := manager.Query().Equals("ifname", ifname).CountWithError() + if err != nil { + return "", errors.Wrap(err, "CountWithError") + } + if cnt == 0 { + return ifname, nil + } + } + return "", errors.Wrap(httperrors.ErrTooManyAttempts, "maximal retry reached") +} + +func (manager *SNetTapServiceManager) GenerateMac(suggestion string) (string, error) { + return generateMac(suggestion) +} + +func (manager *SNetTapServiceManager) FilterByMac(mac string) *sqlchemy.SQuery { + return manager.Query().Equals("mac_addr", mac) +} diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index 5c5d933935..9c06153b11 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -243,6 +243,9 @@ func InitHandlers(app *appsrv.Application) { models.InstanceBackupManager, models.IPv6GatewayManager, + + models.NetTapServiceManager, + models.NetTapFlowManager, } { db.RegisterModelManager(manager) handler := db.NewModelHandler(manager) diff --git a/pkg/hostman/hostinfo/hostinfo.go b/pkg/hostman/hostinfo/hostinfo.go index d4973a364e..945fdf1138 100644 --- a/pkg/hostman/hostinfo/hostinfo.go +++ b/pkg/hostman/hostinfo/hostinfo.go @@ -40,6 +40,7 @@ import ( identityapi "yunion.io/x/onecloud/pkg/apis/identity" napi "yunion.io/x/onecloud/pkg/apis/notify" "yunion.io/x/onecloud/pkg/cloudcommon/notifyclient" + "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver" "yunion.io/x/onecloud/pkg/hostman/host_health" deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis" @@ -1337,6 +1338,18 @@ func (h *SHostInfo) getNetworkInfo() { } func (h *SHostInfo) uploadNetworkInfo() { + phyNics, err := sysutils.Nics() + if err != nil { + h.onFail(errors.Wrap(err, "parse physical nics info")) + return + } + for _, pnic := range phyNics { + err := h.doSendPhysicalNicInfo(pnic) + if err != nil { + h.onFail(errors.Wrapf(err, "doSendPhysicalNicInfo %s", pnic.Dev)) + return + } + } for _, nic := range h.Nics { if len(nic.WireId) == 0 { if len(nic.Network) == 0 { @@ -1352,30 +1365,57 @@ func (h *SHostInfo) uploadNetworkInfo() { return } else { nic.Network, _ = wireInfo.GetString("name") - h.doUploadNicInfo(nic) + err := h.doUploadNicInfo(nic) + if err != nil { + h.onFail(errors.Wrapf(err, "doUploadNicInfo %s", nic.Inter)) + return + } } - } else { - h.doUploadNicInfo(nic) + err := h.doUploadNicInfo(nic) + if err != nil { + h.onFail(errors.Wrapf(err, "doUploadNicInfo %s", nic.Inter)) + return + } } } else { - h.doSyncNicInfo(nic) + err := h.doSyncNicInfo(nic) + if err != nil { + h.onFail(errors.Wrapf(err, "doSyncNicInfo %s", nic.Inter)) + return + } } } h.getStoragecacheInfo() } -func (h *SHostInfo) doUploadNicInfo(nic *SNIC) { - log.Infof("Upload NIC br:%s if:%s", nic.Bridge, nic.Inter) +func (h *SHostInfo) doSendPhysicalNicInfo(nic *types.SNicDevInfo) error { + return h.doUploadNicInfoInternal(nic.Dev, nic.Mac.String(), "", "", "", nic.Up != nil && *nic.Up) +} + +func (h *SHostInfo) doUploadNicInfo(nic *SNIC) error { + err := h.doUploadNicInfoInternal(nic.Inter, nic.BridgeDev.GetMac(), nic.Network, nic.Bridge, nic.Ip, true) + if err != nil { + return errors.Wrap(err, "doUploadNicInfoInternal") + } + return h.onUploadNicInfoSucc(nic) +} + +func (h *SHostInfo) doUploadNicInfoInternal(ifname, mac, net, bridge, ipaddr string, isUp bool) error { + log.Infof("Upload NIC br:%s if:%s", bridge, ifname) content := jsonutils.NewDict() - content.Set("mac", jsonutils.NewString(nic.BridgeDev.GetMac())) - content.Set("wire", jsonutils.NewString(nic.Network)) - content.Set("bridge", jsonutils.NewString(nic.Bridge)) - content.Set("interface", jsonutils.NewString(nic.Inter)) - content.Set("link_up", jsonutils.JSONTrue) - if len(nic.Ip) > 0 { - content.Set("ip_addr", jsonutils.NewString(nic.Ip)) - if nic.Ip == h.GetMasterIp() { + content.Set("mac", jsonutils.NewString(mac)) + content.Set("wire", jsonutils.NewString(net)) + content.Set("bridge", jsonutils.NewString(bridge)) + content.Set("interface", jsonutils.NewString(ifname)) + if isUp { + content.Set("link_up", jsonutils.JSONTrue) + } else { + content.Set("link_up", jsonutils.JSONFalse) + } + if len(ipaddr) > 0 { + content.Set("ip_addr", jsonutils.NewString(ipaddr)) + if ipaddr == h.GetMasterIp() { content.Set("nic_type", jsonutils.NewString(api.NIC_TYPE_ADMIN)) } // always try to allocate from reserved pool @@ -1384,14 +1424,12 @@ func (h *SHostInfo) doUploadNicInfo(nic *SNIC) { _, err := modules.Hosts.PerformAction(h.GetSession(), h.HostId, "add-netif", content) if err != nil { - h.onFail(err) - return - } else { - h.onUploadNicInfoSucc(nic) + return errors.Wrap(err, "modules.Hosts.PerformAction add-netif") } + return nil } -func (h *SHostInfo) doSyncNicInfo(nic *SNIC) { +func (h *SHostInfo) doSyncNicInfo(nic *SNIC) error { content := jsonutils.NewDict() content.Set("bridge", jsonutils.NewString(nic.Bridge)) content.Set("interface", jsonutils.NewString(nic.Inter)) @@ -1400,16 +1438,15 @@ func (h *SHostInfo) doSyncNicInfo(nic *SNIC) { _, err := modules.Hostwires.Update(h.GetSession(), h.HostId, nic.WireId, query, content) if err != nil { - h.onFail(err) - return + return errors.Wrap(err, "modules.Hostwires.Update") } + return nil } -func (h *SHostInfo) onUploadNicInfoSucc(nic *SNIC) { +func (h *SHostInfo) onUploadNicInfoSucc(nic *SNIC) error { res, err := modules.Hostwires.Get(h.GetSession(), h.HostId, nic.Network, nil) if err != nil { - h.onFail(err) - return + return errors.Wrap(err, "modules.Hostwires.Get") } else { bridge, _ := res.GetString("bridge") iface, _ := res.GetString("interface") @@ -1424,10 +1461,10 @@ func (h *SHostInfo) onUploadNicInfoSucc(nic *SNIC) { } nic.SetWireId(wire, wireId, bandwidth) } else { - h.onFail("GetMatchNic failed!!!") - return + return errors.Error("GetMatchNic failed!!!") } } + return nil } func (h *SHostInfo) getStoragecacheInfo() { diff --git a/pkg/mcclient/modules/compute/mod_tap_flows.go b/pkg/mcclient/modules/compute/mod_tap_flows.go new file mode 100644 index 0000000000..f83bbed5d1 --- /dev/null +++ b/pkg/mcclient/modules/compute/mod_tap_flows.go @@ -0,0 +1,35 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var ( + NetTapFlows modulebase.ResourceManager +) + +func init() { + NetTapFlows = modules.NewComputeManager("tap_flow", "tap_flows", + []string{ + "id", "name", "enabled", "type", "tap", "tap_id", "source_id", "source", "source_ips", "net_id", "net", "vlan_id", "mac_addr", "direction", + }, + []string{}, + ) + + modules.RegisterCompute(&NetTapFlows) +} diff --git a/pkg/mcclient/modules/compute/mod_tap_services.go b/pkg/mcclient/modules/compute/mod_tap_services.go new file mode 100644 index 0000000000..c1a01fdc8f --- /dev/null +++ b/pkg/mcclient/modules/compute/mod_tap_services.go @@ -0,0 +1,35 @@ +// 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 compute + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var ( + NetTapServices modulebase.ResourceManager +) + +func init() { + NetTapServices = modules.NewComputeManager("tap_service", "tap_services", + []string{ + "id", "name", "enabled", "type", "target_id", "target", "target_ips", "mac_addr", "flow_count", + }, + []string{}, + ) + + modules.RegisterCompute(&NetTapServices) +} diff --git a/pkg/mcclient/options/compute/net_tap_flows.go b/pkg/mcclient/options/compute/net_tap_flows.go new file mode 100644 index 0000000000..7ea0dcdef0 --- /dev/null +++ b/pkg/mcclient/options/compute/net_tap_flows.go @@ -0,0 +1,52 @@ +// 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 compute + +import ( + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type NetTapFlowCreateOptions struct { + api.NetTapFlowCreateInput +} + +func (o *NetTapFlowCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(o), nil +} + +type NetTapFlowListOptions struct { + options.BaseListOptions + + TapId string `help:"filter by tap id" json:"tap_id"` +} + +func (o *NetTapFlowListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(o) +} + +type NetTapFlowIdOptions struct { + ID string `json:"-" help:"Id or name of net tap service"` +} + +func (o *NetTapFlowIdOptions) GetId() string { + return o.ID +} + +func (o *NetTapFlowIdOptions) Params() (jsonutils.JSONObject, error) { + return options.StructToParams(o) +} diff --git a/pkg/mcclient/options/compute/net_tap_services.go b/pkg/mcclient/options/compute/net_tap_services.go new file mode 100644 index 0000000000..36e7203b9b --- /dev/null +++ b/pkg/mcclient/options/compute/net_tap_services.go @@ -0,0 +1,50 @@ +// 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 compute + +import ( + "yunion.io/x/jsonutils" + + api "yunion.io/x/onecloud/pkg/apis/compute" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +type NetTapServiceCreateOptions struct { + api.NetTapServiceCreateInput +} + +func (o *NetTapServiceCreateOptions) Params() (jsonutils.JSONObject, error) { + return jsonutils.Marshal(o), nil +} + +type NetTapServiceListOptions struct { + options.BaseListOptions +} + +func (o *NetTapServiceListOptions) Params() (jsonutils.JSONObject, error) { + return options.ListStructToParams(o) +} + +type NetTapServiceIdOptions struct { + ID string `json:"-" help:"Id or name of net tap service"` +} + +func (o *NetTapServiceIdOptions) GetId() string { + return o.ID +} + +func (o *NetTapServiceIdOptions) Params() (jsonutils.JSONObject, error) { + return options.StructToParams(o) +} diff --git a/pkg/util/seclib2/hash.go b/pkg/util/seclib2/hash.go new file mode 100644 index 0000000000..f4e03ac4df --- /dev/null +++ b/pkg/util/seclib2/hash.go @@ -0,0 +1,35 @@ +// 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 seclib2 + +import ( + "crypto/sha256" + "encoding/binary" + "fmt" +) + +func HashId(seed string, idx byte, width int) string { + h := sha256.New() + h.Write([]byte(seed)) + if idx > 0 { + h.Write([]byte{idx}) + } + sum := h.Sum(nil) + numStr := fmt.Sprintf(fmt.Sprintf("%%0%dx", width), binary.BigEndian.Uint64(sum[:8])) + if len(numStr) > width { + numStr = numStr[:width] + } + return numStr +} diff --git a/pkg/util/seclib2/hash_test.go b/pkg/util/seclib2/hash_test.go new file mode 100644 index 0000000000..76c36595f1 --- /dev/null +++ b/pkg/util/seclib2/hash_test.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 seclib2 + +import "testing" + +func TestHashId(t *testing.T) { + for i := 0; i < 256; i++ { + t.Log(HashId("test", byte(i), 6)) + } +} diff --git a/pkg/util/sysutils/kvm.go b/pkg/util/sysutils/kvm.go index 1410d05fe0..aad853ea7b 100644 --- a/pkg/util/sysutils/kvm.go +++ b/pkg/util/sysutils/kvm.go @@ -182,6 +182,10 @@ func unloadKvmModule(name string) bool { func GetKernelModuleParameter(name, moduel string) string { pa := path.Join("/sys/module/", strings.Replace(name, "-", "_", -1), "/parameters/", moduel) + return GetSysConfig(pa) +} + +func GetSysConfig(pa string) string { if f, err := os.Stat(pa); err == nil { if f.IsDir() { return "" @@ -218,7 +222,7 @@ func SetSysConfig(cpath, val string) bool { log.Errorln(err) return false } - if string(oval) != val { + if strings.TrimSpace(string(oval)) != val { err = fileutils2.FilePutContents(cpath, val, false) if err == nil { return true diff --git a/pkg/util/sysutils/nics.go b/pkg/util/sysutils/nics.go new file mode 100644 index 0000000000..df3072d90a --- /dev/null +++ b/pkg/util/sysutils/nics.go @@ -0,0 +1,76 @@ +// 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 sysutils + +import ( + "io/ioutil" + "net" + "os" + "path/filepath" + "strconv" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/httperrors" +) + +const ( + sysNetPath = "/sys/class/net" +) + +func Nics() ([]*types.SNicDevInfo, error) { + if _, err := os.Stat(sysNetPath); !os.IsNotExist(err) { + nicDevs, err := ioutil.ReadDir(sysNetPath) + if err != nil { + log.Errorf("ReadDir %s error: %s", sysNetPath, err) + return nil, errors.Wrapf(err, "ioutil.ReadDir(%s)", sysNetPath) + } + nics := make([]*types.SNicDevInfo, 0) + for _, nic := range nicDevs { + netPath := filepath.Join(sysNetPath, nic.Name()) + if _, err := os.Stat(filepath.Join(netPath, "device")); os.IsNotExist(err) { + continue + } + speedStr := GetSysConfig(filepath.Join(netPath, "speed")) + speed := 0 + if len(speedStr) > 0 { + speed, _ = strconv.Atoi(speedStr) + } + carrier := GetSysConfig(filepath.Join(netPath, "carrier")) + up := false + if carrier == "1" { + up = true + } + mac, _ := net.ParseMAC(GetSysConfig(filepath.Join(netPath, "address"))) + mtuStr := GetSysConfig(filepath.Join(netPath, "mtu")) + mtu := 0 + if len(mtuStr) > 0 { + mtu, _ = strconv.Atoi(mtuStr) + } + nicInfo := &types.SNicDevInfo{ + Dev: nic.Name(), + Mac: mac, + Speed: speed, + Up: &up, + Mtu: mtu, + } + nics = append(nics, nicInfo) + } + return nics, nil + } + return nil, errors.Wrapf(httperrors.ErrNotSupported, "no such dir %s", sysNetPath) +} diff --git a/pkg/util/sysutils/nics_test.go b/pkg/util/sysutils/nics_test.go new file mode 100644 index 0000000000..8bb884084c --- /dev/null +++ b/pkg/util/sysutils/nics_test.go @@ -0,0 +1,30 @@ +// 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 sysutils + +import ( + "testing" + + "yunion.io/x/jsonutils" +) + +func TestNics(t *testing.T) { + nics, err := Nics() + if err != nil { + t.Errorf("error %s", err) + } else { + t.Logf("%s", jsonutils.Marshal(nics)) + } +}