diff --git a/cmd/climc/shell/routetables.go b/cmd/climc/shell/routetables.go new file mode 100644 index 0000000000..32f0207830 --- /dev/null +++ b/cmd/climc/shell/routetables.go @@ -0,0 +1,117 @@ +package shell + +import ( + "fmt" + "strings" + + "yunion.io/x/jsonutils" + + "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/mcclient/modules" + "yunion.io/x/onecloud/pkg/mcclient/options" +) + +func init() { + printRouteTableList := func(list *modules.ListResult, columns []string) { + data := list.Data + for _, jsonObj := range data { + jd := jsonObj.(*jsonutils.JSONDict) + routesObj, err := jd.GetArray("routes") + if err != nil { + continue + } + routes := []string{} + for _, routeObj := range routesObj { + typ, _ := routeObj.GetString("type") + cidr, _ := routeObj.GetString("cidr") + next_hop_type, _ := routeObj.GetString("next_hop_type") + next_hop, _ := routeObj.GetString("next_hop") + route := fmt.Sprintf("%8s: %18s %s", typ, cidr, next_hop_type) + if len(next_hop) > 0 { + route += fmt.Sprintf(":%s", next_hop) + } + routes = append(routes, route) + } + s := strings.Join(routes, "\n") + jd.Set("routes", jsonutils.NewString(s)) + } + printList(list, columns) + } + + R(&options.RouteTableCreateOptions{}, "routetable-create", "Create routetable", func(s *mcclient.ClientSession, opts *options.RouteTableCreateOptions) error { + params, err := opts.Params() + if err != nil { + return err + } + routetable, err := modules.RouteTables.Create(s, params) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) + R(&options.RouteTableGetOptions{}, "routetable-show", "Show routetable", func(s *mcclient.ClientSession, opts *options.RouteTableGetOptions) error { + routetable, err := modules.RouteTables.Get(s, opts.ID, nil) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) + R(&options.RouteTableListOptions{}, "routetable-list", "List routetables", func(s *mcclient.ClientSession, opts *options.RouteTableListOptions) error { + params, err := options.ListStructToParams(opts) + if err != nil { + return err + } + result, err := modules.RouteTables.List(s, params) + if err != nil { + return err + } + printRouteTableList(result, modules.RouteTables.GetColumns(s)) + return nil + }) + R(&options.RouteTableUpdateOptions{}, "routetable-update", "Update routetable", func(s *mcclient.ClientSession, opts *options.RouteTableUpdateOptions) error { + params, err := opts.Params() + if err != nil { + return err + } + routetable, err := modules.RouteTables.Update(s, opts.ID, params) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) + R(&options.RouteTableAddRoutesOptions{}, "routetable-add-routes", "Add routes to routetable", func(s *mcclient.ClientSession, opts *options.RouteTableAddRoutesOptions) error { + params, err := opts.Params() + if err != nil { + return err + } + routetable, err := modules.RouteTables.PerformAction(s, opts.ID, "add-routes", params) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) + R(&options.RouteTableDelRoutesOptions{}, "routetable-del-routes", "Del routes to routetable", func(s *mcclient.ClientSession, opts *options.RouteTableDelRoutesOptions) error { + params, err := opts.Params() + if err != nil { + return err + } + routetable, err := modules.RouteTables.PerformAction(s, opts.ID, "del-routes", params) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) + R(&options.RouteTableDeleteOptions{}, "routetable-delete", "Show routetable", func(s *mcclient.ClientSession, opts *options.RouteTableDeleteOptions) error { + routetable, err := modules.RouteTables.Delete(s, opts.ID, nil) + if err != nil { + return err + } + printObjectRecursive(routetable) + return nil + }) +} diff --git a/pkg/compute/handlers.go b/pkg/compute/handlers.go index 6d7ed755f0..ad2ec0a97a 100644 --- a/pkg/compute/handlers.go +++ b/pkg/compute/handlers.go @@ -71,6 +71,7 @@ func InitHandlers(app *appsrv.Application) { models.LoadbalancerCertificateManager, models.LoadbalancerAclManager, models.LoadbalancerAgentManager, + models.RouteTableManager, models.SchedpolicyManager, models.DynamicschedtagManager, diff --git a/pkg/compute/models/routetables.go b/pkg/compute/models/routetables.go new file mode 100644 index 0000000000..a79e59219c --- /dev/null +++ b/pkg/compute/models/routetables.go @@ -0,0 +1,254 @@ +package models + +import ( + "context" + "net" + "reflect" + "strings" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/pkg/gotypes" + "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" +) + +type SRoute struct { + Type string + Cidr string + NextHopType string + NextHopId string +} + +func (route *SRoute) Validate(data *jsonutils.JSONDict) error { + if strings.Index(route.Cidr, "/") > 0 { + _, ipNet, err := net.ParseCIDR(route.Cidr) + if err != nil { + return err + } + // normalize from 192.168.1.3/24 to 192.168.1.0/24 + route.Cidr = ipNet.String() + } else { + ip := net.ParseIP(route.Cidr).To4() + if ip == nil { + return httperrors.NewInputParameterError("invalid addr %s", route.Cidr) + } + } + return nil +} + +type SRoutes []*SRoute + +func (routes *SRoutes) String() string { + return jsonutils.Marshal(routes).String() +} +func (routes *SRoutes) IsZero() bool { + if len([]*SRoute(*routes)) == 0 { + return true + } + return false +} + +func (routes *SRoutes) Validate(data *jsonutils.JSONDict) error { + found := map[string]bool{} + for _, route := range *routes { + if err := route.Validate(data); err != nil { + return err + } + if _, ok := found[route.Cidr]; ok { + // error so that the user has a chance to deal with comments + return httperrors.NewInputParameterError("duplicate route cidr %s", route.Cidr) + } + // TODO aliyun: check overlap with System type route + found[route.Cidr] = true + } + return nil +} + +type SRouteTableManager struct { + db.SVirtualResourceBaseManager +} + +var RouteTableManager *SRouteTableManager + +func init() { + gotypes.RegisterSerializable(reflect.TypeOf(&SRoutes{}), func() gotypes.ISerializable { + return &SRoutes{} + }) + RouteTableManager = &SRouteTableManager{ + SVirtualResourceBaseManager: db.NewVirtualResourceBaseManager( + SRouteTable{}, + "route_tables_tbl", + "route_table", + "route_tables", + ), + } +} + +type SRouteTable struct { + db.SVirtualResourceBase + + VpcId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"required"` + CloudregionId string `width:"36" charset:"ascii" nullable:"false" list:"user" create:"optional"` + Type string `width:"16" charset:"ascii" nullable:"false" list:"user"` + Routes *SRoutes `list:"user" update:"user" create:"required"` +} + +func (man *SRouteTableManager) ListItemFilter(ctx context.Context, q *sqlchemy.SQuery, userCred mcclient.TokenCredential, query jsonutils.JSONObject) (*sqlchemy.SQuery, error) { + q, err := man.SVirtualResourceBaseManager.ListItemFilter(ctx, q, userCred, query) + if err != nil { + return nil, err + } + userProjId := userCred.GetProjectId() + data := query.(*jsonutils.JSONDict) + for _, key := range []string{"vpc", "cloudregion"} { + v := validators.NewModelIdOrNameValidator(key, key, userProjId) + v.Optional(true) + q, err = v.QueryFilter(q, data) + if err != nil { + return nil, err + } + } + return q, nil +} + +func (man *SRouteTableManager) validateRoutes(data *jsonutils.JSONDict, update bool) (*jsonutils.JSONDict, error) { + routes := SRoutes{} + routesV := validators.NewStructValidator("routes", &routes) + if update { + routesV.Optional(true) + } + err := routesV.Validate(data) + if err != nil { + return nil, err + } + return data, nil +} + +func (man *SRouteTableManager) ValidateCreateData(ctx context.Context, userCred mcclient.TokenCredential, ownerProjId string, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + data, err := man.validateRoutes(data, false) + if err != nil { + return nil, err + } + vpcV := validators.NewModelIdOrNameValidator("vpc", "vpc", ownerProjId) + if err := vpcV.Validate(data); err != nil { + return nil, err + } + vpc := vpcV.Model.(*SVpc) + cloudregion := vpc.GetRegion() + if cloudregion == nil { + return nil, httperrors.NewConflictError("failed fetching cloudregion of vpc %s(%s)", vpc.Name, vpc.Id) + } + data.Set("cloudregion_id", jsonutils.NewString(cloudregion.Id)) + return man.SVirtualResourceBaseManager.ValidateCreateData(ctx, userCred, ownerProjId, query, data) +} + +func (rt *SRouteTable) ValidateUpdateData(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + data, err := RouteTableManager.validateRoutes(data, true) + if err != nil { + return nil, err + } + return rt.SVirtualResourceBase.ValidateUpdateData(ctx, userCred, query, data) +} + +func (rt *SRouteTable) AllowPerformAddRoutes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) bool { + return rt.IsOwner(userCred) || userCred.IsSystemAdmin() +} + +func (rt *SRouteTable) AllowPerformDelRoutes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) bool { + return rt.AllowPerformAddRoutes(ctx, userCred, query, data) +} + +// PerformAddRoutes patches acl entries by adding then deleting the specified acls. +// This is intended mainly for command line operations. +func (rt *SRouteTable) PerformAddRoutes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + routes := gotypes.DeepCopy(rt.Routes).(SRoutes) + { + adds := SRoutes{} + addsV := validators.NewStructValidator("routes", &adds) + addsV.Optional(true) + err := addsV.Validate(data) + if err != nil { + return nil, err + } + for _, add := range adds { + found := false + for _, route := range routes { + if route.Cidr == add.Cidr { + found = true + break + } + } + if !found { + routes = append(routes, add) + } + } + } + _, err := rt.GetModelManager().TableSpec().Update(rt, func() error { + rt.Routes = &routes + return nil + }) + if err != nil { + return nil, err + } + return nil, nil +} + +func (rt *SRouteTable) PerformDelRoutes(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data *jsonutils.JSONDict) (*jsonutils.JSONDict, error) { + routes := gotypes.DeepCopy(rt.Routes).(SRoutes) + { + cidrs := []string{} + err := data.Unmarshal(&cidrs, "cidrs") + if err != nil { + return nil, httperrors.NewInputParameterError("unmarshaling cidrs failed: %s", err) + } + for _, cidr := range cidrs { + for i := len(routes) - 1; i >= 0; i-- { + route := routes[i] + if route.Type == "system" { + continue + } + if route.Cidr == cidr { + routes = append(routes[:i], routes[i+1:]...) + break + } + } + } + } + _, err := rt.GetModelManager().TableSpec().Update(rt, func() error { + rt.Routes = &routes + return nil + }) + if err != nil { + return nil, err + } + return nil, nil +} + +func (rt *SRouteTable) GetCustomizeColumns(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := rt.SVirtualResourceBase.GetCustomizeColumns(ctx, userCred, query) + vpcM, err := VpcManager.FetchById(rt.VpcId) + if err != nil { + log.Errorf("route table %s(%s): fetch vpc (%s) error: %s", + rt.Name, rt.Id, rt.VpcId, err) + return extra + } + cloudregionM, err := CloudregionManager.FetchById(rt.CloudregionId) + if err != nil { + log.Errorf("route table %s(%s): fetch cloud region (%s) error: %s", + rt.Name, rt.Id, rt.CloudregionId, err) + return extra + } + extra.Set("vpc", jsonutils.NewString(vpcM.GetName())) + extra.Set("cloudregion", jsonutils.NewString(cloudregionM.GetName())) + return extra +} + +func (rt *SRouteTable) GetExtraDetails(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) *jsonutils.JSONDict { + extra := rt.GetCustomizeColumns(ctx, userCred, query) + return extra +} diff --git a/pkg/mcclient/modules/mod_routetables.go b/pkg/mcclient/modules/mod_routetables.go new file mode 100644 index 0000000000..12f965ecad --- /dev/null +++ b/pkg/mcclient/modules/mod_routetables.go @@ -0,0 +1,28 @@ +package modules + +type RouteTableManager struct { + ResourceManager +} + +var ( + RouteTables RouteTableManager +) + +func init() { + RouteTables = RouteTableManager{ + NewComputeManager( + "route_table", + "route_tables", + []string{ + "id", + "name", + "type", + "vpc", + "vpc_id", + "routes", + }, + []string{"tenant"}, + ), + } + registerCompute(&RouteTables) +} diff --git a/pkg/mcclient/options/routetables.go b/pkg/mcclient/options/routetables.go new file mode 100644 index 0000000000..c214d824b8 --- /dev/null +++ b/pkg/mcclient/options/routetables.go @@ -0,0 +1,133 @@ +package options + +import ( + "fmt" + + "yunion.io/x/jsonutils" +) + +type Route struct { + Type string + Cidr string + NextHopType string + NextHopId string +} + +type Routes []*Route + +type RoutesOptions struct { + RouteType []string + RouteCidr []string + RouteNextHopType []string + RouteNextHopId []string +} + +func (opts *RoutesOptions) Params() (jsonutils.JSONObject, error) { + len0 := len(opts.RouteType) + len1 := len(opts.RouteCidr) + if len0 != len1 || len0 != len(opts.RouteNextHopType) || len1 != len(opts.RouteNextHopId) { + return nil, fmt.Errorf("there must be equal number of options of --route-xxx") + } + routes := []*Route{} + for i := 0; i < len0; i++ { + routes = append(routes, &Route{ + Type: opts.RouteType[i], + Cidr: opts.RouteCidr[i], + NextHopType: opts.RouteNextHopType[i], + NextHopId: opts.RouteNextHopId[i], + }) + } + routesJson := jsonutils.Marshal(routes) + return routesJson, nil +} + +type RouteTableCreateOptions struct { + NAME string + Vpc string + + RoutesOptions +} + +func (opts *RouteTableCreateOptions) Params() (*jsonutils.JSONDict, error) { + params, err := optionsStructToParams(opts) + if err != nil { + return nil, err + } + routesJson, err := opts.RoutesOptions.Params() + if err != nil { + return nil, err + } + params.Set("routes", routesJson) + return params, nil +} + +type RouteTableGetOptions struct { + ID string +} + +type RouteTableUpdateOptions struct { + ID string `json:"-"` + Name string + + RoutesOptions +} + +func (opts *RouteTableUpdateOptions) Params() (*jsonutils.JSONDict, error) { + params, err := optionsStructToParams(opts) + if err != nil { + return nil, err + } + if len(opts.RouteCidr) != 0 { + routesJson, err := opts.RoutesOptions.Params() + if err != nil { + return nil, err + } + params.Set("routes", routesJson) + } + return params, nil +} + +type RouteTableAddRoutesOptions struct { + ID string `json:"-"` + + RoutesOptions +} + +func (opts *RouteTableAddRoutesOptions) Params() (*jsonutils.JSONDict, error) { + if len(opts.RouteCidr) == 0 { + return nil, fmt.Errorf("nothing to add") + } + routesJson, err := opts.RoutesOptions.Params() + if err != nil { + return nil, err + } + params := jsonutils.NewDict() + params.Set("routes", routesJson) + return params, nil +} + +type RouteTableDelRoutesOptions struct { + ID string `json:"-"` + + RouteCidr []string +} + +func (opts *RouteTableDelRoutesOptions) Params() (*jsonutils.JSONDict, error) { + if len(opts.RouteCidr) == 0 { + return nil, fmt.Errorf("nothing to del") + } + params := jsonutils.NewDict() + params.Set("cidrs", jsonutils.Marshal(opts.RouteCidr)) + return params, nil +} + +type RouteTableDeleteOptions struct { + ID string +} + +type RouteTableListOptions struct { + Vpc string + Cloudregion string + + BaseListOptions +}