diff --git a/cmd/climc/shell/compute/network_ip_mac.go b/cmd/climc/shell/compute/network_ip_mac.go new file mode 100644 index 0000000000..3cda210db3 --- /dev/null +++ b/cmd/climc/shell/compute/network_ip_mac.go @@ -0,0 +1,33 @@ +package compute + +import ( + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/cmd/climc/shell" + "yunion.io/x/onecloud/pkg/mcclient" + modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + + cmd := shell.NewResourceCmd(&modules.NetworkIpMacs) + cmd.List(&options.NetworkIpMacListOptions{}) + cmd.Update(&options.NetworkIpMacUpdateOptions{}) + cmd.Show(&options.NetworkIpMacIdOptions{}) + cmd.Delete(&options.NetworkIpMacIdOptions{}) + cmd.Create(&options.NetworkIpMacCreateOptions{}) + type NetworkIpMacBatchCreateOptions struct { + NETWORK string `help:"network id" json:"network_id"` + IpMac map[string]string `help:"ip mac map" json:"ip_mac"` + } + R(&NetworkIpMacBatchCreateOptions{}, + "network-ip-mac-batch-create", + "Network ip mac bind batch create", + func(s *mcclient.ClientSession, args *NetworkIpMacBatchCreateOptions) error { + params := jsonutils.Marshal(args) + _, err := modules.NetworkIpMacs.PerformClassAction(s, "batch-create", params) + return err + }, + ) +} diff --git a/pkg/apis/compute/network.go b/pkg/apis/compute/network.go index f3053c5664..9aa1255913 100644 --- a/pkg/apis/compute/network.go +++ b/pkg/apis/compute/network.go @@ -69,6 +69,14 @@ type NetworkFilterListInput struct { NetworkFilterListBase } +type NetworkIpMacListInput struct { + apis.StandaloneAnonResourceListInput + + NetworkId string `json:"network_id"` + MacAddr []string `json:"mac_addr"` + IpAddr []string `json:"ip_addr"` +} + type NetworkListInput struct { apis.SharableVirtualResourceListInput apis.ExternalizedResourceBaseListInput @@ -151,6 +159,14 @@ type NetworkResourceInfo struct { WireResourceInfo } +type NetworkIpMacCreateInput struct { + apis.StandaloneAnonResourceCreateInput + + NetworkId string `json:"network_id"` + MacAddr string `json:"mac_addr"` + IpAddr string `json:"ip_addr"` +} + type NetworkCreateInput struct { apis.SharableVirtualResourceCreateInput @@ -280,6 +296,14 @@ type NetworkDetails struct { Schedtags []SchedtagShortDescDetails `json:"schedtags"` } +type NetworkIpMacDetails struct { + apis.StandaloneAnonResourceDetails + + NetworkId string `json:"network_id"` + IpAddr string `json:"ip_addr"` + MacAddr string `json:"mac_addr"` +} + type NetworkReserveIpInput struct { apis.Meta @@ -345,6 +369,13 @@ type NetworkSyncInput struct { apis.Meta } +type NetworkIpMacUpdateInput struct { + apis.StandaloneAnonResourceBaseUpdateInput + + MacAddr string `json:"mac_addr"` + IpAddr string `json:"ip_addr"` +} + type NetworkUpdateInput struct { apis.SharableVirtualResourceBaseUpdateInput @@ -392,3 +423,8 @@ type NetworkSetBgpTypeInput struct { // example: ChinaTelecom, BGP, etc. BgpType string `json:"bgp_type"` } + +type NetworkIpMacBatchCreateInput struct { + NetworkId string `json:"network_id"` + IpMac map[string]string `json:"ip_mac"` +} diff --git a/pkg/compute/models/guests.go b/pkg/compute/models/guests.go index 4bef58a056..06674f692b 100644 --- a/pkg/compute/models/guests.go +++ b/pkg/compute/models/guests.go @@ -3194,6 +3194,9 @@ func (args *Attach2NetworkArgs) onceArgs(i int) attach2NetworkOnceArgs { pendingUsage: args.PendingUsage, } + if ipBindMac := NetworkIpMacManager.GetMacFromIp(r.network.Id, r.ipAddr); ipBindMac != "" { + r.nicConf.Mac = ipBindMac + } if i > 0 { r.ipAddr = "" r.bwLimit = 0 diff --git a/pkg/compute/models/initdb.go b/pkg/compute/models/initdb.go index ea242a37fa..933ba3c464 100644 --- a/pkg/compute/models/initdb.go +++ b/pkg/compute/models/initdb.go @@ -43,6 +43,7 @@ func InitDB() error { SecurityGroupCacheManager, NetworkManager, NetworkAddressManager, + NetworkIpMacManager, GuestManager, HostManager, LoadbalancerCertificateManager, diff --git a/pkg/compute/models/network_ip_mac.go b/pkg/compute/models/network_ip_mac.go new file mode 100644 index 0000000000..0766f0a0f7 --- /dev/null +++ b/pkg/compute/models/network_ip_mac.go @@ -0,0 +1,276 @@ +package models + +import ( + "context" + "database/sql" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + "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/httperrors" + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/stringutils2" +) + +type SNetworkIpMacManager struct { + db.SStandaloneAnonResourceBaseManager +} + +var NetworkIpMacManager *SNetworkIpMacManager + +func GetNetworkIpMacManager() *SNetworkIpMacManager { + if NetworkIpMacManager != nil { + return NetworkIpMacManager + } + NetworkIpMacManager = &SNetworkIpMacManager{ + SStandaloneAnonResourceBaseManager: db.NewStandaloneAnonResourceBaseManager( + SNetworkIpMac{}, + "network_ip_macs_tbl", + "network_ip_mac", + "network_ip_macs", + ), + } + NetworkIpMacManager.SetVirtualObject(NetworkIpMacManager) + return NetworkIpMacManager +} + +func init() { + GetNetworkIpMacManager() +} + +type SNetworkIpMac struct { + db.SStandaloneAnonResourceBase + + NetworkId string `width:"36" charset:"ascii" nullable:"false" list:"user" index:"true" create:"required"` + // MAC地址 + MacAddr string `width:"32" charset:"ascii" nullable:"false" list:"user" index:"true" create:"required"` + // IPv4地址 + IpAddr string `width:"16" charset:"ascii" nullable:"false" list:"user" index:"true" create:"required"` +} + +func (manager *SNetworkIpMacManager) NetworkMacAddrInUse(networkId, macAddr string) (bool, error) { + count, err := manager.Query(). + Equals("network_id", networkId). + Equals("mac_addr", macAddr).CountWithError() + if err != nil { + return false, err + } + return count > 0, nil +} + +func (manager *SNetworkIpMacManager) NetworkIpAddrInUse(networkId, ipAddr string) (bool, error) { + count, err := manager.Query(). + Equals("network_id", networkId). + Equals("ip_addr", ipAddr).CountWithError() + if err != nil { + return false, err + } + return count > 0, nil +} + +func (manager *SNetworkIpMacManager) ValidateCreateData( + ctx context.Context, userCred mcclient.TokenCredential, + ownerId mcclient.IIdentityProvider, query jsonutils.JSONObject, input api.NetworkIpMacCreateInput, +) (api.NetworkIpMacCreateInput, error) { + if input.NetworkId == "" { + return input, httperrors.NewMissingParameterError("network_id") + } + if input.IpAddr == "" { + return input, httperrors.NewMissingParameterError("ip_addr") + } + if input.MacAddr == "" { + return input, httperrors.NewMissingParameterError("mac_addr") + } + + iNetwork, err := NetworkManager.FetchByIdOrName(userCred, input.NetworkId) + if err == sql.ErrNoRows { + return input, httperrors.NewNotFoundError("network %s not found", input.NetworkId) + } else if err != nil { + return input, errors.Wrap(err, "fetch network") + } + network := iNetwork.(*SNetwork) + if network.ServerType != api.NETWORK_TYPE_GUEST { + return input, httperrors.NewBadRequestError("network type %s can't set ip mac", network.ServerType) + } + + input.NetworkId = network.Id + input.MacAddr = strings.ToLower(input.MacAddr) + return input, manager.validateIpMac(input.IpAddr, input.MacAddr, network) +} + +func (self *SNetworkIpMac) ValidateUpdateData( + ctx context.Context, userCred mcclient.TokenCredential, + query jsonutils.JSONObject, input api.NetworkIpMacUpdateInput, +) (api.NetworkIpMacUpdateInput, error) { + if input.IpAddr == "" && input.MacAddr == "" { + return input, httperrors.NewInputParameterError("missing update field") + } + + if input.IpAddr != "" { + iNetwork, err := NetworkManager.FetchByIdOrName(userCred, self.NetworkId) + if err != nil { + return input, errors.Wrap(err, "fetch network") + } + network := iNetwork.(*SNetwork) + if !network.Contains(input.IpAddr) { + return input, errors.Errorf("network %s not contains ip addr %s", network.GetName(), input.IpAddr) + } + if ipInUse, err := NetworkIpMacManager.NetworkIpAddrInUse(self.NetworkId, input.IpAddr); err != nil { + return input, errors.Wrap(err, "check ip addr in use") + } else if ipInUse { + return input, errors.Errorf("ip addr %s is in use", input.IpAddr) + } + } else { + input.IpAddr = self.IpAddr + } + + if input.MacAddr != "" { + if !utils.IsMatchMacAddr(input.MacAddr) { + return input, errors.Errorf("mac address %s is not valid", input.MacAddr) + } + input.MacAddr = strings.ToLower(input.MacAddr) + if macInUse, err := NetworkIpMacManager.NetworkMacAddrInUse(self.NetworkId, input.MacAddr); err != nil { + return input, errors.Wrap(err, "check mac addr in use") + } else if macInUse { + return input, errors.Errorf("mac addr %s is in use", input.MacAddr) + } + } else { + input.MacAddr = self.MacAddr + } + + if gn, err := GuestnetworkManager.getGuestNicByIP(self.NetworkId, input.IpAddr); err != nil { + return input, errors.Wrap(err, "failed get guest nic") + } else if gn != nil && gn.MacAddr != input.MacAddr { + return input, errors.Errorf("input ip mac conflict with guest %s nic %d", gn.GuestId, gn.Index) + } + + return input, nil +} + +func (manager *SNetworkIpMacManager) ListItemFilter( + ctx context.Context, + q *sqlchemy.SQuery, + userCred mcclient.TokenCredential, + input api.NetworkIpMacListInput, +) (*sqlchemy.SQuery, error) { + var err error + + q, err = manager.SStandaloneAnonResourceBaseManager.ListItemFilter(ctx, q, userCred, input.StandaloneAnonResourceListInput) + if err != nil { + return nil, errors.Wrap(err, "SStandaloneAnonResourceBaseManager.ListItemFilter") + } + if input.NetworkId != "" { + iNetwork, err := NetworkManager.FetchByIdOrName(userCred, input.NetworkId) + if err != nil { + return q, errors.Wrap(err, "fetch network") + } + q = q.Equals("network_id", iNetwork.GetId()) + } + if input.MacAddr != nil { + q = q.In("mac_addr", input.MacAddr) + } + if input.IpAddr != nil { + q = q.In("ip_addr", input.IpAddr) + } + return q, nil +} + +func (manager *SNetworkIpMacManager) GetMacFromIp(networkId, ipAddr string) string { + q := manager.Query("mac_addr").Equals("network_id", networkId).Equals("ip_addr", ipAddr) + if q.Count() != 1 { + return "" + } + var nim = new(SNetworkIpMac) + if err := q.First(nim); err != nil { + log.Errorf("failed get mac addr form ip %s: %s", ipAddr, err) + return "" + } + return nim.MacAddr +} + +func (manager *SNetworkIpMacManager) validateIpMac(ip, mac string, network *SNetwork) error { + if !network.Contains(ip) { + return httperrors.NewBadRequestError("network %s not contains ip addr %s", network.GetName(), ip) + } + if ipInUse, err := manager.NetworkIpAddrInUse(network.Id, ip); err != nil { + return errors.Wrap(err, "check ip addr in use") + } else if ipInUse { + return httperrors.NewBadRequestError("ip addr %s is in use", ip) + } + + if !utils.IsMatchMacAddr(mac) { + return httperrors.NewBadRequestError("mac address %s is not valid", mac) + } + if macInUse, err := manager.NetworkMacAddrInUse(network.Id, mac); err != nil { + return errors.Wrap(err, "check mac addr in use") + } else if macInUse { + return httperrors.NewBadRequestError("mac addr %s is in use", mac) + } + + if gn, err := GuestnetworkManager.getGuestNicByIP(network.Id, ip); err != nil { + return errors.Wrap(err, "failed get guest nic") + } else if gn != nil && gn.MacAddr != mac { + return httperrors.NewBadRequestError("input ip mac conflict with guest %s nic %d", gn.GuestId, gn.Index) + } + return nil +} + +func (manager *SNetworkIpMacManager) PerformBatchCreate( + ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, input *api.NetworkIpMacBatchCreateInput, +) (jsonutils.JSONObject, error) { + iNetwork, err := NetworkManager.FetchByIdOrName(userCred, input.NetworkId) + if err != nil { + return nil, errors.Wrap(err, "fetch network") + } + network := iNetwork.(*SNetwork) + if network.ServerType != api.NETWORK_TYPE_GUEST { + return nil, errors.Errorf("network type %s can't set ip mac", network.ServerType) + } + input.NetworkId = network.Id + + var errs = []error{} + for ip, mac := range input.IpMac { + if err := manager.validateIpMac(ip, mac, network); err != nil { + errs = append(errs, err) + continue + } + nim := &SNetworkIpMac{ + NetworkId: input.NetworkId, + IpAddr: ip, + MacAddr: mac, + } + if err := manager.TableSpec().Insert(ctx, nim); err != nil { + errs = append(errs, err) + continue + } + } + if len(errs) == 0 { + return nil, nil + } + return nil, errors.NewAggregate(errs) +} +func (manager *SNetworkIpMacManager) FetchCustomizeColumns( + ctx context.Context, + userCred mcclient.TokenCredential, + query jsonutils.JSONObject, + objs []interface{}, + fields stringutils2.SSortedStrings, + isList bool, +) []api.NetworkIpMacDetails { + rows := make([]api.NetworkIpMacDetails, len(objs)) + + baseRows := manager.SStandaloneAnonResourceBaseManager.FetchCustomizeColumns(ctx, userCred, query, objs, fields, isList) + + for i := range rows { + rows[i] = api.NetworkIpMacDetails{ + StandaloneAnonResourceDetails: baseRows[i], + } + } + return rows +} diff --git a/pkg/compute/service/handlers.go b/pkg/compute/service/handlers.go index 57c6159a2f..8c2f6868b1 100644 --- a/pkg/compute/service/handlers.go +++ b/pkg/compute/service/handlers.go @@ -124,6 +124,7 @@ func InitHandlers(app *appsrv.Application) { models.DiskManager, models.NetworkManager, models.NetworkAddressManager, + models.NetworkIpMacManager, models.ReservedipManager, models.KeypairManager, models.IsolatedDeviceManager, diff --git a/pkg/mcclient/modules/compute/mod_network_ip_macs.go b/pkg/mcclient/modules/compute/mod_network_ip_macs.go new file mode 100644 index 0000000000..c34b9ae916 --- /dev/null +++ b/pkg/mcclient/modules/compute/mod_network_ip_macs.go @@ -0,0 +1,18 @@ +package compute + +import ( + "yunion.io/x/onecloud/pkg/mcclient/modulebase" + "yunion.io/x/onecloud/pkg/mcclient/modules" +) + +var ( + NetworkIpMacs modulebase.ResourceManager +) + +func init() { + NetworkIpMacs = modules.NewComputeManager("network_ip_mac", "network_ip_macs", + []string{"ID", "Network_id", "Ip_addr", "Mac_addr"}, + []string{}) + + modules.RegisterCompute(&NetworkIpMacs) +} diff --git a/pkg/mcclient/options/network.go b/pkg/mcclient/options/network.go index 8c92640c4a..95c273ab83 100644 --- a/pkg/mcclient/options/network.go +++ b/pkg/mcclient/options/network.go @@ -16,6 +16,7 @@ package options import ( "yunion.io/x/jsonutils" + "yunion.io/x/pkg/errors" "yunion.io/x/onecloud/cmd/climc/shell" ) @@ -151,3 +152,61 @@ func (opts *NetworkIdOptions) GetId() string { func (opts *NetworkIdOptions) Params() (jsonutils.JSONObject, error) { return nil, nil } + +type NetworkIpMacIdOptions struct { + ID string `help:"ID or Name of the network_ip_mac to show"` +} + +func (opts *NetworkIpMacIdOptions) GetId() string { + return opts.ID +} + +func (opts *NetworkIpMacIdOptions) Params() (jsonutils.JSONObject, error) { + return nil, nil +} + +type NetworkIpMacListOptions struct { + BaseListOptions + + Network string `help:"search networks" json:"network_id"` + MacAddr []string `help:"search by mac addr"` + IpAddr []string `help:"search by ip addr"` +} + +func (opts *NetworkIpMacListOptions) Params() (jsonutils.JSONObject, error) { + return ListStructToParams(opts) +} + +type NetworkIpMacUpdateOptions struct { + ID string `help:"ID or Name of resource to update"` + + MacAddr string `help:"update mac addr"` + IpAddr string `help:"update ip addr"` +} + +func (opts *NetworkIpMacUpdateOptions) GetId() string { + return opts.ID +} + +func (opts *NetworkIpMacUpdateOptions) Params() (jsonutils.JSONObject, error) { + return ListStructToParams(opts) +} + +type NetworkIpMacCreateOptions struct { + NETWORK string `help:"network id" json:"network_id"` + MACADDR string `help:"mac address" json:"mac_addr"` + IPADDR string `help:"ip address" json:"ip_addr"` +} + +func (opts *NetworkIpMacCreateOptions) Params() (jsonutils.JSONObject, error) { + if opts.NETWORK == "" { + return nil, errors.Errorf("missing network params") + } + if opts.MACADDR == "" { + return nil, errors.Errorf("missing mac_addr params") + } + if opts.IPADDR == "" { + return nil, errors.Errorf("missing ip_addr params") + } + return ListStructToParams(opts) +}