// 2020 Rackspace, Inc. // // 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 migMeta import ( "context" "encoding/json" "fmt" "net" "time" redisPkg "github.com/go-redis/redis/v7" "github.rackspace.com/DCM/tools" "go.mongodb.org/mongo-driver/bson/primitive" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/bson" ) const ( // Collection defines the collection name Collection = "migrationMetadata" // MetaKeyNetworkDrac defines the metadata key for source drac network subnets MetaKeyNetworkDrac = "network.drac" // MetaKeyNetworkBackup defines the metadata key for source backup network subnets MetaKeyNetworkBackup = "network.backup" // RedisKey is the unique identifier to use with our cache RedisKey = "dcm.tools.migMeta" dracNet = "DRACNet" serviceNet = "ServiceNet" vmNet = "VMNet" redisMigTTL = 28800 ) type ( // Migration defines metadata for an overall migration (instead of a move group // like the metadata collection). // ID matches the device collection name (e.g. "dfw3_01") Migration struct { ID string `json:"_id" bson:"_id"` FiltersCollection string `json:"filters_collection" bson:"filters_collection"` DataCenter string `json:"data_center" bson:"data_center"` DefaultTimezone string `json:"default_timezone" bson:"default_timezone"` ManualImport bool `json:"manual_import" bson:"manual_import"` Name string `json:"name" bson:"name"` Metadata []Meta `json:"metadata" bson:"metadata"` Description string `json:"description" bson:"description"` FullDC bool `json:"full_dc_migration" bson:"full_dc_migration"` Active bool `json:"active" bson:"active"` // Type defines if the overall migration type is Rackspace or an alternate (i.e., 'datapipe'). // This has consequences on the source of truth for environment and switch port data, for // portmaps, and for vlan translations; could effectively also be called SourceDCType. Type string `json:"type" bson:"type"` // RackspaceMigrationDC defines an overall boolean on whether the migration DC for the // move groups will be a Rackspace-managed DC; this has consequences on the source // of truth for migration data such as migration switch ports. // If false: only primary IPs will be imported from DCX tooling, the rest from the linked CMDB RackspaceMigrationDC bool `json:"rackspace_migration_dc" bson:"rackspace_migration_dc"` ZoneSwings ZoneSwings `json:"zone_swings" bson:"zone_swings"` } // ZoneSwings will define source zone(s) where neteng is swinging services to a migration zone // rather than standing up replacement SVIs. The string entry should match the zone name exactly // as it is in Network Architect (ex: "ORD1:ServiceNet:Zone601-23"). ZoneSwings struct { DRACNet []string `json:"dracNet" bson:"dracNet"` ServiceNet []string `json:"serviceNet" bson:"serviceNet"` VMNet []string `json:"vmNet" bson:"vmNet"` } // Meta is a key:value pair that exist in the migrationMetadata collection // used to hold arbitrary information about a migration Meta struct { Key string `json:"key" bson:"key"` Value interface{} `json:"value" bson:"value"` } //Client is the client for clients to clientize with Client struct { db *mongo.Client redis *redisPkg.Client } // Opt type is a set of optional arguments that // could be included into the network package. Opt func(c *Client) ) // WithCache will initialize the client with a redis cache. // This will allow the subsequent GET calls to use a redis cache. func WithCache(r *redisPkg.Client) Opt { return func(c *Client) { c.redis = r } } // New will initialize the clients needed for the migMeta package. func New(db *mongo.Client, opts ...Opt) *Client { c := &Client{db: db} for _, opt := range opts { opt(c) } return c } // Get will fetch data from the migrationMetadata collection func (c *Client) Get(query bson.M) ([]Migration, error) { // Use Redis to fetch cached data if c.redis != nil { if m, err := c.redis.Get(RedisKey).Result(); err == nil { resp := make([]Migration, 0) if err = json.Unmarshal([]byte(m), &resp); err == nil { return resp, nil } } } resp := make([]Migration, 0) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() cursor, err := c.db.Database(tools.MigrationDB).Collection(Collection).Find(ctx, query) if err != nil { return resp, err } if err = cursor.All(ctx, &resp); err != nil { return resp, err } // Use Redis to store cached data if c.redis != nil { if s, err := json.Marshal(&resp); err == nil { _ = c.redis.Set(RedisKey, string(s), redisMigTTL*time.Second) } } return resp, nil } // GetSubnets returns the parsed list of IP networks in the // matching metaKey field func (m *Migration) GetSubnets(metaKey string) []net.IPNet { nets := make([]net.IPNet, 0) for _, meta := range m.Metadata { if meta.Key == metaKey { v, ok := meta.Value.(primitive.A) if ok { for _, v2 := range v { v3, ok := v2.(primitive.D) if ok && len(v3) > 0 { var netmask, network string for _, e := range v3 { switch e.Key { case "netmask": netmask, _ = e.Value.(string) case "network": network, _ = e.Value.(string) } } ip := net.ParseIP(netmask).To4() if ip == nil { continue } mask := net.IPv4Mask(ip[0], ip[1], ip[2], ip[3]) ones, _ := mask.Size() _, ipNet, err := net.ParseCIDR(fmt.Sprintf("%s/%d", network, ones)) if err != nil || ipNet == nil { continue } nets = append(nets, *ipNet) } } } } } return nets } // IPInSubnets is intended to be called with the results of GetSubnets(), so the // []net.IPNet list only needs to be built once func (m *Migration) IPInSubnets(nets []net.IPNet, ip string) bool { x := net.ParseIP(ip) if x == nil { return false } for _, ipNet := range nets { if ipNet.Contains(x) { return true } } return false } // ZoneSwingsForType is a helper function to return zone swings for a specific port type func (m *Migration) ZoneSwingsForType(portType string) []string { switch portType { case dracNet: return m.ZoneSwings.DRACNet case serviceNet: return m.ZoneSwings.ServiceNet case vmNet: return m.ZoneSwings.VMNet } return []string{} }