Compare commits

...

6 Commits

Author SHA1 Message Date
M. Mert Yıldıran
5fc3e38c1a Fix the Kafka ApiKey query and add ApiKeyName field (human-readable ApiKey) (#1080)
* Fix the Kafka `ApiKey` query and add `ApiKeyName` field (human-readable `ApiKey`)

* Update the dataset for Kafka unit tests

* #run_acceptance_tests
2022-05-15 09:42:32 +03:00
RoyUP9
09a0fca2c2 Extracted insert to database functionality (#1082) 2022-05-15 09:19:33 +03:00
M. Mert Yıldıran
0437586908 Replace the gRPC reference link with a better one (#1081) 2022-05-14 19:43:43 +03:00
M. Mert Yıldıran
f8181ccb07 Remove ReassemblyStream interface (duplicate of gopacket/reassembly.Stream) (#1079) 2022-05-14 18:37:16 +03:00
leon-up9
414e5cfe5a match selectlist ui (#1077)
* insert filter and header to selectlist

* handle single select

* rename search var

* font size changed
2022-05-11 19:51:12 +03:00
RoyUP9
2fac0009ea Fixed oas query (#1076) 2022-05-11 15:36:46 +03:00
16 changed files with 133 additions and 69 deletions

View File

@@ -373,6 +373,7 @@ func handleIncomingMessageAsTapper(socketConnection *websocket.Conn) {
func initializeDependencies() { func initializeDependencies() {
dependency.RegisterGenerator(dependency.ServiceMapGeneratorDependency, func() interface{} { return servicemap.GetDefaultServiceMapInstance() }) dependency.RegisterGenerator(dependency.ServiceMapGeneratorDependency, func() interface{} { return servicemap.GetDefaultServiceMapInstance() })
dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() }) dependency.RegisterGenerator(dependency.OasGeneratorDependency, func() interface{} { return oas.GetDefaultOasGeneratorInstance() })
dependency.RegisterGenerator(dependency.EntriesInserter, func() interface{} { return api.GetBasenineEntryInserterInstance() })
dependency.RegisterGenerator(dependency.EntriesProvider, func() interface{} { return &entries.BasenineEntriesProvider{} }) dependency.RegisterGenerator(dependency.EntriesProvider, func() interface{} { return &entries.BasenineEntriesProvider{} })
dependency.RegisterGenerator(dependency.EntriesSocketStreamer, func() interface{} { return &api.BasenineEntryStreamer{} }) dependency.RegisterGenerator(dependency.EntriesSocketStreamer, func() interface{} { return &api.BasenineEntryStreamer{} })
dependency.RegisterGenerator(dependency.EntryStreamerSocketConnector, func() interface{} { return &api.DefaultEntryStreamerSocketConnector{} }) dependency.RegisterGenerator(dependency.EntryStreamerSocketConnector, func() interface{} { return &api.DefaultEntryStreamerSocketConnector{} })

View File

@@ -25,10 +25,7 @@ import (
"github.com/up9inc/mizu/agent/pkg/utils" "github.com/up9inc/mizu/agent/pkg/utils"
"github.com/up9inc/mizu/logger" "github.com/up9inc/mizu/logger"
"github.com/up9inc/mizu/shared"
tapApi "github.com/up9inc/mizu/tap/api" tapApi "github.com/up9inc/mizu/tap/api"
basenine "github.com/up9inc/basenine/client/go"
) )
var k8sResolver *resolver.Resolver var k8sResolver *resolver.Resolver
@@ -103,20 +100,6 @@ func startReadingChannel(outputItems <-chan *tapApi.OutputChannelItem, extension
panic("Channel of captured messages is nil") panic("Channel of captured messages is nil")
} }
BasenineReconnect:
connection, err := basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
if err != nil {
logger.Log.Errorf("Can't establish a new connection to Basenine server: %v", err)
time.Sleep(shared.BasenineReconnectInterval * time.Second)
goto BasenineReconnect
}
if err = connection.InsertMode(); err != nil {
logger.Log.Errorf("Insert mode call failed: %v", err)
connection.Close()
time.Sleep(shared.BasenineReconnectInterval * time.Second)
goto BasenineReconnect
}
disableOASValidation := false disableOASValidation := false
ctx := context.Background() ctx := context.Background()
doc, contractContent, router, err := loadOAS(ctx) doc, contractContent, router, err := loadOAS(ctx)
@@ -163,11 +146,9 @@ BasenineReconnect:
providers.EntryAdded(len(data)) providers.EntryAdded(len(data))
if err = connection.SendText(string(data)); err != nil { entryInserter := dependency.GetInstance(dependency.EntriesInserter).(EntryInserter)
logger.Log.Errorf("An error occured while inserting a new record to database: %v", err) if err := entryInserter.Insert(mizuEntry); err != nil {
connection.Close() logger.Log.Errorf("Error inserting entry, err: %v", err)
time.Sleep(shared.BasenineReconnectInterval * time.Second)
goto BasenineReconnect
} }
serviceMapGenerator := dependency.GetInstance(dependency.ServiceMapGeneratorDependency).(servicemap.ServiceMapSink) serviceMapGenerator := dependency.GetInstance(dependency.ServiceMapGeneratorDependency).(servicemap.ServiceMapSink)

View File

@@ -0,0 +1,71 @@
package api
import (
"encoding/json"
"fmt"
basenine "github.com/up9inc/basenine/client/go"
"github.com/up9inc/mizu/logger"
"github.com/up9inc/mizu/shared"
"github.com/up9inc/mizu/tap/api"
"sync"
"time"
)
type EntryInserter interface {
Insert(entry *api.Entry) error
}
type BasenineEntryInserter struct {
connection *basenine.Connection
}
var instance *BasenineEntryInserter
var once sync.Once
func GetBasenineEntryInserterInstance() *BasenineEntryInserter {
once.Do(func() {
instance = &BasenineEntryInserter{}
})
return instance
}
func (e *BasenineEntryInserter) Insert(entry *api.Entry) error {
if e.connection == nil {
e.connection = initializeConnection()
}
data, err := json.Marshal(entry)
if err != nil {
return fmt.Errorf("error marshling entry, err: %v", err)
}
if err := e.connection.SendText(string(data)); err != nil {
e.connection.Close()
e.connection = nil
return fmt.Errorf("error sending text to database, err: %v", err)
}
return nil
}
func initializeConnection() *basenine.Connection{
for {
connection, err := basenine.NewConnection(shared.BasenineHost, shared.BaseninePort)
if err != nil {
logger.Log.Errorf("Can't establish a new connection to Basenine server: %v", err)
time.Sleep(shared.BasenineReconnectInterval * time.Second)
continue
}
if err = connection.InsertMode(); err != nil {
logger.Log.Errorf("Insert mode call failed: %v", err)
connection.Close()
time.Sleep(shared.BasenineReconnectInterval * time.Second)
continue
}
return connection
}
}

View File

@@ -5,6 +5,7 @@ type DependencyContainerType string
const ( const (
ServiceMapGeneratorDependency = "ServiceMapGeneratorDependency" ServiceMapGeneratorDependency = "ServiceMapGeneratorDependency"
OasGeneratorDependency = "OasGeneratorDependency" OasGeneratorDependency = "OasGeneratorDependency"
EntriesInserter = "EntriesInserter"
EntriesProvider = "EntriesProvider" EntriesProvider = "EntriesProvider"
EntriesSocketStreamer = "EntriesSocketStreamer" EntriesSocketStreamer = "EntriesSocketStreamer"
EntryStreamerSocketConnector = "EntryStreamerSocketConnector" EntryStreamerSocketConnector = "EntryStreamerSocketConnector"

View File

@@ -104,7 +104,7 @@ func (g *defaultOasGenerator) runGenerator() {
g.dbMutex.Lock() g.dbMutex.Lock()
defer g.dbMutex.Unlock() defer g.dbMutex.Unlock()
logger.Log.Infof("Querying DB for OAS generator with query '%s'", g.entriesQuery) logger.Log.Infof("Querying DB for OAS generator with query '%s'", g.entriesQuery)
if err := g.dbConn.Query("", g.entriesQuery, dataChan, metaChan); err != nil { if err := g.dbConn.Query("latest", g.entriesQuery, dataChan, metaChan); err != nil {
logger.Log.Errorf("Query mode call failed: %v", err) logger.Log.Errorf("Query mode call failed: %v", err)
} }

View File

@@ -327,7 +327,7 @@ BasenineReconnect:
go handleMetaChannel(&wg, connection, meta) go handleMetaChannel(&wg, connection, meta)
wg.Add(2) wg.Add(2)
if err = connection.Query("", query, data, meta); err != nil { if err = connection.Query("latest", query, data, meta); err != nil {
logger.Log.Errorf("Query mode call failed: %v", err) logger.Log.Errorf("Query mode call failed: %v", err)
connection.Close() connection.Close()
time.Sleep(shared.BasenineReconnectInterval * time.Second) time.Sleep(shared.BasenineReconnectInterval * time.Second)

View File

@@ -66,7 +66,7 @@ var grpcProtocol api.Protocol = api.Protocol{
BackgroundColor: "#244c5a", BackgroundColor: "#244c5a",
ForegroundColor: "#ffffff", ForegroundColor: "#ffffff",
FontSize: 11, FontSize: 11,
ReferenceLink: "https://grpc.github.io/grpc/core/md_doc_statuscodes.html", ReferenceLink: "https://grpc.github.io/grpc/core/md_doc__p_r_o_t_o_c_o_l-_h_t_t_p2.html",
Ports: []string{"80", "443", "8080", "50051"}, Ports: []string{"80", "443", "8080", "50051"},
Priority: 0, Priority: 0,
} }

View File

@@ -13,4 +13,4 @@ test-pull-bin:
test-pull-expect: test-pull-expect:
@mkdir -p expect @mkdir -p expect
@[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect8/kafka/\* expect @[ "${skipexpect}" ] && echo "Skipping downloading expected JSONs" || gsutil -o 'GSUtil:parallel_process_count=5' -o 'GSUtil:parallel_thread_count=5' -m cp -r gs://static.up9.io/mizu/test-pcap/expect9/kafka/\* expect

View File

@@ -3,13 +3,14 @@ package kafka
import ( import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"reflect" "reflect"
"sort" "sort"
"strconv" "strconv"
"strings" "strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"github.com/fatih/camelcase" "github.com/fatih/camelcase"
"github.com/ohler55/ojg/jp" "github.com/ohler55/ojg/jp"
"github.com/ohler55/ojg/oj" "github.com/ohler55/ojg/oj"
@@ -36,9 +37,14 @@ type KafkaWrapper struct {
func representRequestHeader(data map[string]interface{}, rep []interface{}) []interface{} { func representRequestHeader(data map[string]interface{}, rep []interface{}) []interface{} {
requestHeader, _ := json.Marshal([]api.TableData{ requestHeader, _ := json.Marshal([]api.TableData{
{
Name: "ApiKeyName",
Value: data["apiKeyName"].(string),
Selector: `request.apiKeyName`,
},
{ {
Name: "ApiKey", Name: "ApiKey",
Value: apiNames[int(data["apiKey"].(float64))], Value: int(data["apiKey"].(float64)),
Selector: `request.apiKey`, Selector: `request.apiKey`,
}, },
{ {

View File

@@ -96,8 +96,8 @@ func (d dissecting) Summarize(entry *api.Entry) *api.BaseEntry {
statusQuery := "" statusQuery := ""
apiKey := ApiKey(entry.Request["apiKey"].(float64)) apiKey := ApiKey(entry.Request["apiKey"].(float64))
method := apiNames[apiKey] method := entry.Request["apiKeyName"].(string)
methodQuery := fmt.Sprintf("request.apiKey == %d", int(entry.Request["apiKey"].(float64))) methodQuery := fmt.Sprintf(`request.apiKeyName == "%s"`, method)
summary := "" summary := ""
summaryQuery := "" summaryQuery := ""

View File

@@ -11,6 +11,7 @@ import (
type Request struct { type Request struct {
Size int32 `json:"size"` Size int32 `json:"size"`
ApiKeyName string `json:"apiKeyName"`
ApiKey ApiKey `json:"apiKey"` ApiKey ApiKey `json:"apiKey"`
ApiVersion int16 `json:"apiVersion"` ApiVersion int16 `json:"apiVersion"`
CorrelationID int32 `json:"correlationID"` CorrelationID int32 `json:"correlationID"`
@@ -202,6 +203,7 @@ func ReadRequest(r io.Reader, tcpID *api.TcpID, counterPair *api.CounterPair, ca
request := &Request{ request := &Request{
Size: size, Size: size,
ApiKeyName: apiNames[apiKey],
ApiKey: apiKey, ApiKey: apiKey,
ApiVersion: apiVersion, ApiVersion: apiVersion,
CorrelationID: correlationID, CorrelationID: correlationID,

View File

@@ -10,12 +10,6 @@ import (
"github.com/up9inc/mizu/tap/diagnose" "github.com/up9inc/mizu/tap/diagnose"
) )
type ReassemblyStream interface {
Accept(tcp *layers.TCP, ci gopacket.CaptureInfo, dir reassembly.TCPFlowDirection, nextSeq reassembly.Sequence, start *bool, ac reassembly.AssemblerContext) bool
ReassembledSG(sg reassembly.ScatterGather, ac reassembly.AssemblerContext)
ReassemblyComplete(ac reassembly.AssemblerContext) bool
}
type tcpReassemblyStream struct { type tcpReassemblyStream struct {
ident string ident string
tcpState *reassembly.TCPSimpleFSM tcpState *reassembly.TCPSimpleFSM
@@ -25,7 +19,7 @@ type tcpReassemblyStream struct {
tcpStream api.TcpStream tcpStream api.TcpStream
} }
func NewTcpReassemblyStream(ident string, tcp *layers.TCP, fsmOptions reassembly.TCPSimpleFSMOptions, stream api.TcpStream) ReassemblyStream { func NewTcpReassemblyStream(ident string, tcp *layers.TCP, fsmOptions reassembly.TCPSimpleFSMOptions, stream api.TcpStream) reassembly.Stream {
return &tcpReassemblyStream{ return &tcpReassemblyStream{
ident: ident, ident: ident,
tcpState: reassembly.NewTCPSimpleFSM(fsmOptions), tcpState: reassembly.NewTCPSimpleFSM(fsmOptions),

View File

@@ -96,8 +96,3 @@
& .servicesFilterList & .servicesFilterList
height: calc(100% - 30px - 52px) height: calc(100% - 30px - 52px)
.totalSelected
font-size: 12px
color: $light-blue-color
font-weight: 700

View File

@@ -68,7 +68,6 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
const [checkedProtocols, setCheckedProtocols] = useState([]) const [checkedProtocols, setCheckedProtocols] = useState([])
const [checkedServices, setCheckedServices] = useState([]) const [checkedServices, setCheckedServices] = useState([])
const [serviceMapApiData, setServiceMapApiData] = useState<ServiceMapGraph>({ edges: [], nodes: [] }) const [serviceMapApiData, setServiceMapApiData] = useState<ServiceMapGraph>({ edges: [], nodes: [] })
const [servicesSearchVal, setServicesSearchVal] = useState("")
const [graphOptions, setGraphOptions] = useState(ServiceMapOptions); const [graphOptions, setGraphOptions] = useState(ServiceMapOptions);
const [isFilterClicked, setIsFilterClicked] = useState(true) const [isFilterClicked, setIsFilterClicked] = useState(true)
@@ -219,22 +218,14 @@ export const ServiceMapModal: React.FC<ServiceMapModalProps> = ({ isOpen, onClos
<Resizeable minWidth={170} maxWidth={320}> <Resizeable minWidth={170} maxWidth={320}>
<div className={styles.filterWrapper}> <div className={styles.filterWrapper}>
<div className={styles.protocolsFilterList}> <div className={styles.protocolsFilterList}>
<h3 className={styles.subSectionHeader} style={{ marginLeft: "10px" }}> <SelectList items={getProtocolsForFilter} checkBoxWidth="5%" tableName={"PROTOCOLS"} multiSelect={true}
PROTOCOLS checkedValues={checkedProtocols} setCheckedValues={onProtocolsChange} tableClassName={styles.filters}
<span className={styles.totalSelected}>&nbsp;({checkedProtocols.length})</span> inputSearchClass={styles.servicesFilterSearch} isFilterable={false}/>
</h3>
<SelectList items={getProtocolsForFilter} checkBoxWidth="5%" tableName={"All"} multiSelect={true}
checkedValues={checkedProtocols} setCheckedValues={onProtocolsChange} tableClassName={styles.filters} />
</div> </div>
<div className={styles.servicesFilter}> <div className={styles.servicesFilter}>
<h3 className={styles.subSectionHeader} style={{ marginLeft: "10px" }}> <div className={styles.servicesFilterList}>
SERVICES <SelectList items={getServicesForFilter} tableName={"SERVICES"} tableClassName={styles.filters} multiSelect={true}
<span className={styles.totalSelected}>&nbsp;({checkedServices.length})</span> checkBoxWidth="5%" checkedValues={checkedServices} setCheckedValues={onServiceChanges} inputSearchClass={styles.servicesFilterSearch}/>
</h3>
<input className={commonClasses.textField + ` ${styles.servicesFilterSearch}`} placeholder="Search" value={servicesSearchVal} onChange={(event) => setServicesSearchVal(event.target.value)} />
<div className={styles.servicesFilterList}>
<SelectList items={getServicesForFilter} tableName={"All"} tableClassName={styles.filters} multiSelect={true} searchValue={servicesSearchVal}
checkBoxWidth="5%" checkedValues={checkedServices} setCheckedValues={onServiceChanges} />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,6 +3,7 @@ import Radio from "./Radio";
import styles from './style/SelectList.module.sass' import styles from './style/SelectList.module.sass'
import NoDataMessage from "./NoDataMessage"; import NoDataMessage from "./NoDataMessage";
import Checkbox from "./Checkbox"; import Checkbox from "./Checkbox";
import { useCommonStyles } from "../../helpers/commonStyle";
export interface Props { export interface Props {
@@ -10,14 +11,17 @@ export interface Props {
tableName: string; tableName: string;
checkedValues?: string[]; checkedValues?: string[];
multiSelect: boolean; multiSelect: boolean;
searchValue?: string;
setCheckedValues: (newValues) => void; setCheckedValues: (newValues) => void;
tableClassName? tableClassName?;
checkBoxWidth?: string checkBoxWidth?: string;
inputSearchClass? : string
isFilterable? : boolean
} }
const SelectList: React.FC<Props> = ({ items, tableName, checkedValues = [], multiSelect = true, searchValue = "", setCheckedValues, tableClassName, const SelectList: React.FC<Props> = ({ items, tableName, checkedValues = [], multiSelect = true, setCheckedValues, tableClassName,
checkBoxWidth = 50 }) => { checkBoxWidth = 50 ,inputSearchClass,isFilterable = true}) => {
const commonClasses = useCommonStyles();
const [searchValue, setSearchValue] = useState("")
const noItemsMessage = "No items to show"; const noItemsMessage = "No items to show";
const [headerChecked, setHeaderChecked] = useState(false) const [headerChecked, setHeaderChecked] = useState(false)
@@ -73,11 +77,10 @@ const SelectList: React.FC<Props> = ({ items, tableName, checkedValues = [], mul
<th style={{ width: checkBoxWidth }}><Checkbox data-cy="checkbox-all" checked={headerChecked} <th style={{ width: checkBoxWidth }}><Checkbox data-cy="checkbox-all" checked={headerChecked}
onToggle={(isChecked) => toggleAll(isChecked)} /></th> onToggle={(isChecked) => toggleAll(isChecked)} /></th>
<th> <th>
{tableName} All
</th> </th>
</tr> : </tr> :
<tr style={{ borderBottomWidth: "2px" }}> <tr>
<th>{tableName}</th>
</tr> </tr>
const tableBody = filteredValues.length === 0 ? const tableBody = filteredValues.length === 0 ?
@@ -100,7 +103,14 @@ const SelectList: React.FC<Props> = ({ items, tableName, checkedValues = [], mul
} }
) )
return <div className={tableClassName ? tableClassName + ` ${styles.selectListTable}` : ` ${styles.selectListTable}`}> return <React.Fragment>
<h3 className={styles.subSectionHeader}>
{tableName}
<span className={styles.totalSelected}>&nbsp;({checkedValues.length})</span>
</h3>
{isFilterable && <input className={commonClasses.textField + ` ${inputSearchClass}`} placeholder="Search" value={searchValue}
onChange={(event) => setSearchValue(event.target.value)} data-cy="searchInput" />}
<div className={tableClassName ? tableClassName + ` ${styles.selectListTable}` : ` ${styles.selectListTable}`} style={{marginTop: !multiSelect ? "20px": ""}}>
<table cellPadding={5} style={{ borderCollapse: "collapse" }}> <table cellPadding={5} style={{ borderCollapse: "collapse" }}>
<thead> <thead>
{tableHead} {tableHead}
@@ -110,6 +120,7 @@ const SelectList: React.FC<Props> = ({ items, tableName, checkedValues = [], mul
</tbody> </tbody>
</table> </table>
</div> </div>
</React.Fragment>
} }
export default SelectList; export default SelectList;

View File

@@ -1,4 +1,6 @@
@import '../../../variables.module' @import '../../../variables.module'
@import '../../../components'
.selectListTable .selectListTable
overflow: auto overflow: auto
@@ -17,6 +19,7 @@
position: sticky position: sticky
top: 0 top: 0
background: $main-background-color background: $main-background-color
font-size: 12px
tr tr
border-bottom-width: 1px border-bottom-width: 1px
@@ -27,7 +30,15 @@
td td
color: $light-gray color: $light-gray
padding: 10px padding: 10px
font-size: 16px font-size: 11px
font-weight: 600
padding-top: 5px
padding-bottom: 5px
.nowrap .nowrap
white-space: nowrap white-space: nowrap
.totalSelected
font-size: 12px
color: $light-blue-color
font-weight: 700